The conclusion of the analysis:
C# Wasm AOT still
has a long way to become a general and performant client side web
programming platform. Note: The usage of the Uno.Wasm.Bootstrap
toolchain may have affected the performance of some of the benchmarks.
Thus, this analysis should not be regarded as a benchmark of the
finalized product. However, note that the Uno platform is using ".NET 6
WebAssembly Mixed mode AOT/Interpreter" (source).
با سلام؛ در صورتی که قصد ذخیره سازی اطلاعات یک محصول را داشته باشم و یکی از Propertyهای کلاس محصول ImageName باشد، باید نام عکس ارسالی را دریافت و دخیره کنیم، اکشن متد UpoadFiles نام عکس ذخیره شده را برگشت میدهد، حال به چه طریقی میتوان به این نام در اکشن متد Index دسترسی داشت؟ نام برگشتی از اکشن متد UploadFiles را در یک HiddenField قرار میدهم ولی باز هم در اکشن Index در دسترس نیست.
نظرات مطالب
آدرس جدید مخزن کد NHibernate
Git به علت ساختار توزیع شده آن از SVN پیشرفتهتر است. البته مرکوریال هم در همین رده قرار میگیرد. تا جایی که میدونم در گوگل از mercurial زیاد استفاده میشود[+]. لینوکسیها هم از Git ، چون خود Linus Torvalds اون رو درست کرده (یا حداقل شروعش توسط اون بوده).
ولی ... در کل مهم این است که از یک سیستم سورس کنترل استفاده شود. من با SVN راحتم!
ولی ... در کل مهم این است که از یک سیستم سورس کنترل استفاده شود. من با SVN راحتم!
اشتراکها
کامپوننت WPF Designer
اوراکل هم سیستمی مشابه in memory oltp را راه اندازی کرده است که در مقایسه با sql server
Sql server سه برابر سریعتر بوده است.
امروزه اکثر شرکتها چه کوچک و چه بزرگ به سمت sql server حرکت میکنند. این حرکت به دلایل زیر صورت میگیرد
برتری sql server بر اوراکل از لحاظ کارایی
ارزانتر بودن sql server: اوراکل در حال حاضر قیمتی حدود 250 هزار دلار دارد در صورتی که بهترین و گرانترین نسخه sql server قیمتی به مراتب پایینتر داره
Sql server سه برابر سریعتر بوده است.
امروزه اکثر شرکتها چه کوچک و چه بزرگ به سمت sql server حرکت میکنند. این حرکت به دلایل زیر صورت میگیرد
برتری sql server بر اوراکل از لحاظ کارایی
ارزانتر بودن sql server: اوراکل در حال حاضر قیمتی حدود 250 هزار دلار دارد در صورتی که بهترین و گرانترین نسخه sql server قیمتی به مراتب پایینتر داره
Oslo پلتفرم جدید مدلسازی مایکروسافت است که در سالهای آتی مورد استفاده قرار خواهد گرفت و همچنین این روزها در مجامع توسعه و طراحی برنامهها به شدت مورد بحث و توجه است. به همین جهت در طی مقالاتی با این پلتفرم جدید بیشتر آشنا خواهیم شد.
دریافت Oslo
Oslo از سه قسمت عمده تشکیل شده است:
- الف) زبان مدل سازی M
- ب) ابزار مدل سازی Quadrant
- ج) استفاده از SQL Server به عنوان مخزن
زبان مدل سازی M از سه قسمت به نامهای MGraph ، MGrammer و MSchema تشکیل میشود.
MGrammer : گرامر مورد استفاده در SDL را تعریف میکند. Syntax Directed Translation
MSchema : طرح مدل را تعریف خواهد کرد.
MGraph : اگر MSchema بیانگر انواع باشد، MGraph بیانگر وهلهها خواهد بود.
یک مثال:
برنامهی Intellipad را اجرا کنید (فرض بر این است که SDK فوق را نصب کردهاید)
در اینجا حالت را بر روی M Mode قرار دهید (مطابق تصویر) و همچنین از منوی ظاهر شدهی M Mode ، گزینهی Generic T-SQL preview را هم انتخاب کنید.
اولین ماژول ما به صورت زیر است:
module Test1
{
type ApplicationUser
{
UserID : Integer64=AutoNumber();
FirstName :Text#15;
LastName : Text#25;
Password : Text#10;
} where identity UserID;
}
در این مثال شناسهی کاربری از نوع Integer64 خود افزایش یابنده تعریف شده است (نوع identity در اس کیوال سرور).
فیلدهای نام ، نام خانوادگی و کلمهی عبور از نوع متنی با اندازههای مشخص 15 ، 25 و 10 کاراکتر تعریف شدهاند. اگر اندازه مشخص نبود نوع را تنها Text تعریف کنید.
نکته:
1-اگر پس از Text علامت ? قرار گیرد، به معنای فیلدی از نوع nullable خواهد بود و برعکس. زیبایی Intellipad هم در اینجا است که بلافاصله پس از تایپ شما، عبارت T-SQL معادل را تولید میکند.
2-در اینجا UserID به صورت identity معرفی شده است. در زبان ام ، identity همانند primary key در عبارات T-SQL عمل میکند و نباید اشتباه گرفته شود.
تا اینجا فقط یک type تعریف شده است. برای تبدیل آن به یک جدول باید آنرا توسعه داد.
ApplicationUserCollection : ApplicationUser*;
اکنون قصد داریم گروهی از کاربرها را به صورت نمونه ایجاد کنیم:
ApplicationUserCollection
{
//using a named instance
User1 {
FirstName="user1",
LastName="name1",
Password="1@34"
},
User2 {
FirstName="user2",
LastName="name2",
Password="123@4"
},
User3 {
FirstName="user3",
LastName="name3",
Password="56#2"
},
User4 {
FirstName="user4",
LastName="name4",
Password="789@5"
}
}
ادامه دارد ...
عصر Thick Clients
امن سازی برنامههای وب همواره چالش برانگیز بودهاست؛ خصوصا این روزها که نیاز است برنامهها، خارج از دیوارهای یک شرکت نیز در دسترس باشند و توسط انواع و اقسام وسایل ارتباطی مورد استفاده قرار گیرند. در سالهای قبل، عموما برنامههای thick clients مانند WPF و WinForms برای شرکتها توسعه داده میشدند و یا برنامههای وب مانند ASP.NET Web Forms که مبتنی بر سرویسها نبودند. در برنامههای ویندوزی، پس از لاگین شخص به شبکه و دومین داخلی شرکت، عموما از روش Windows Authentication برای مشخص سازی سطوح دسترسی کاربران استفاده میشود. در برنامههای Web Forms نیز بیشتر روش Forms Authentication برای اعتبارسنجی کاربران مرسوم است. امن سازی این نوع برنامهها سادهاست. عموما بر روی یک دومین ارائه میشوند و کوکیهای اعتبارسنجی کاربران برای ارائهی مباحثی مانند single sign-on (داشتن تنها یک صفحهی لاگین برای دسترسی به تمام برنامههای شرکت)، میسر است.
عصر شروع بهکارگیری سرویسهای وب
در سالهای اخیر این شیوهی کاری تغییر کرده و بیشتر بر اساس بکارگیری برنامههای مبتنی بر سرویسها شدهاست. برای مثال برای این مورد استاندارد WS-Security مربوط به WCF ارائه شدهاست که باز هم مرتبط است به برنامههای یک دومین و یا یک Application pool. اگر تعداد دومینها بیشتر شوند و نیاز به ارتباط امن بین آنها باشد، استاندارد SAML 2.0 مورد استفاده قرار میگرفت که هدف از آن، انتقال امن اعتبارسنجی و سطوح دسترسی کاربران بین دومینهای مختلف است. همانطور که ملاحظه میکنید تمام این برنامهها و استانداردها، داخل دیوارهای یک شرکت و یک دومین زندگی میکنند.
عصر شروع بهکارگیری Restful-API's
پس از آن باز هم شیوهی طراحی برنامههای تغییر کرد و شروع به ایجاد Restful-API's و HTTP API's کردیم. اینها دیگر الزاما داخل یک دومین ارائه نمیشوند و گاهی از اوقات حتی تحت کنترل ما هم نیستند. همچنین برنامههای ارائه شده نیز دیگر thick clients نیستند و ممکن است برنامههای سمت کلاینت Angular و یا حتی موبایل باشند که مستقیما با سرویسهای API برنامهها کار میکنند. حتی ممکن است یک API را طراحی کنیم که با یک API دیگر کار میکند.
در این حالت دیگر نمیتوان این APIها را با نگهداری آنها داخل دیوارهای یک شرکت محافظت کرد. اگر قرار است با یک HTTP API کار کنیم، این API باید عمومی باشد و در اینجا دیگر نمیتوان از روش Forms Authentication استفاده کرد. زیرا این روش اعتبارسنجی مختص برنامههای سمت سرور قرار گرفتهی در یک دومین طراحی شدهاست و آنچنان سازگاری با برنامههای سمت کلاینت و موبایل خارج از دیوارهای آن ندارد. همچنین ارسال نام کاربری و کلمهی عبور به ازای هر درخواست نیز روشی بسیار بدوی و نا امن است. اینجا است که عصر امن سازی برنامهها به کمک توکنها شروع میشود. با استفادهی از توکنها، بجای هر بار ارسال نام کاربری و کلمهی عبور به ازای هر درخواست از API، صرفا لیست سطوح دسترسی امضا شدهی به امکاناتی خاص، ارسال میشوند.
عصر شروع بهکارگیری Security Tokens
بنابراین در اینجا نیاز به برنامهای برای تولید توکنها و ارسال آنها به کلاینتها داریم. روش متداول پیاده سازی آن، ساخت یک برنامهی ابتدایی، برای دریافت نام کاربری و کلمهی عبور از کاربران و سپس بازگشت یک JSON Web Token به آنها است که بیانگر سطوح دسترسی آنها به قسمتهای مختلف برنامه است و کاربران باید این توکن را به ازای هر درخواستی به سمت سرور (بجای نام کاربری و کلمهی عبور و خود) ارسال کنند.
مشکل این روش در اینجا است که آن برنامهی خاص، باید از نام کاربری و کلمهی عبور کاربران مطلع باشد تا بتواند توکن مناسبی را برای آن کاربر خاص تولید کند. هر چند این روش برای یک تک برنامهی خاص بسیار مناسب به نظر میرسد، اما در یک شرکت، دهها برنامه مشغول به کارند و به اشتراک گذاری نام کاربری و کلمهی عبور کاربران، با تک تک آنها ایدهی مناسبی نیست و پس از مدتی از کنترل خارج خواهد شد. برای مثال کاربری در یک برنامه، کلمهی عبور خود را تغییر میدهد اما در برنامهای دیگر خیر و همین مسالهی عدم هماهنگی بین برنامهها و همچنین بخشهای مختلف یک شرکت، مدیریت یک دست برنامهها را تقریبا غیر ممکن میکند. همچنین در اینجا برنامههای ثالث را نیز باید در نظر داشت که آیا ضروری است آنها به ریز اطلاعات کاربران شرکت دسترسی پیدا کنند؟
به علاوه مشکل دیگر توسعهی این نوع برنامههای صدور توکن خانگی، اختراع مجدد چرخ است. در اینجا برای بهبود امنیت برنامه باید منقضی شدن، تمدید، امضای دیجیتال و اعتبارسنجی توکنها را خودمان پیاده سازی کنیم. توسعهی یک چنین سیستمی اگر غیرممکن نباشد، بسیار سخت و پیچیده است و همچنین باید باگهای امنیتی ممکن را نیز مدنظر داشت.
بنابراین تا اینجا به این نتیجه رسیدهایم که دیگر نمیخواهیم مدیریت نام کاربری و کلمهی عبور کاربران را در سطح هیچکدام از برنامههای خود انجام دهیم و هیچکدام از آنها قرار نیست دریافت کنندهی این اطلاعات باشند. قرار است این کار، به یک تک برنامهی مرکزی مخصوص اینکار منتقل شود و برای اینکار نیاز به پروتکلی امن است که بتوان این توکنهای تولیدی را ارسال و پردازش کرد.
حرکت به سمت «تامین کنندهی هویت مرکزی»
در گذشته، هر تک برنامهای دارای صفحهی لاگین و امکانات مدیریت کاربران آن، تغییر کلمهی عبور، تنظیم مجدد آن و اینگونه عملیات بود. اینروزها دیگر چنین کاری مرسوم نیست. این وظیفهی برنامهی شما نیست که بررسی کند کاربر وارد شدهی به سیستم کیست و آیا ادعای او صحیح است یا خیر؟ این نوع عملیات وظیفهی یک Identity provider و یا به اختصار IDP است. کار IDP اعتبارسنجی کاربر و در صورت نیاز، ارائهی اثبات هویت کاربر، به برنامهی درخواست کنندهاست.
در یک IDP عملیاتی مانند ثبت کاربران و مدیریت آنها انجام میشود. اینجا است که مفاهیمی مانند قفل کردن اکانت و یا تغییر کلمهی عبور و امثال آن انجام میشود و نه اینکه به ازای هر برنامهی تهیه شدهی برای یک شرکت، آن برنامه راسا اقدام به انجام چنین عملیاتی کند. به این ترتیب میتوان به امکان استفادهی مجدد از اطلاعات هویت کاربران و سطوح دسترسی آنها در بین تمام برنامههای موجود رسید.
همچنین با داشتن یک برنامهی IDP خوب پیاده سازی شده، از توزیع باگهای امنیتی مختلف در بین برنامههای گوناگون تهیه شده که هر کدام سیستم امنیتی خاص خودشان را دارند، جلوگیری خواهد شد. برای مثال فرض کنید میخواهید الگوریتم هش کردن پسوردهای سیستم را که امروز نا امن اعلام شدهاست، تغییر دهید. با داشتن یک IDP، دیگر نیازی نیست تا تمام برنامههای خود را برای رفع یک چنین باگهایی، تک تک تغییر دهید.
به علاوه این روزها روش استفادهی از نام کاربری و کلمهی عبور، تنها راه ورود به یک سیستم نیست و استفاده از کلیدهای دیجیتال و یا روشهای ویژهی ابزارهای موبایل نیز به این لیست اضافه شدهاند.
حرکت به سمت استاندارد OAuth 2
OAuth 2.0 پروتکلی است استاندارد، برای Authorization امن کاربران، توسط برنامههای وب، موبایل و دسکتاپ. به این ترتیب میتوان امکان دسترسی یک برنامه را به یک API، به نحوی استاندارد و امن میسر ساخت. OAuth 2.0 یک توکن دسترسی (Access token) را تولید میکند و در اختیار کلاینت قرار میدهد. سپس آن کلاینت با ارسال این توکن به API مدنظر، امکان دسترسی به امکانات مختلف آنرا خواهد یافت. به علاوه چون ماهیت برنامههای کلاینت وب و غیر وب متفاوت است، این استاندارد نحوهی دریافت چنین توکنی را برای برنامههای مختلف نیز تعریف میکند. به این ترتیب موارد مشترکی مانند تولید و نحوهی انتقال توکنها به کلاینتهای مختلف توسط این پروتکل استاندارد بیان میشود. در این حالت راهحلهای خانگی ما تبدیل به راهحلهایی میشوند که استاندارد OAuth 2.0 را پیاده سازی کرده باشند. بنابراین IDP ما باید بر مبنای این استاندارد تهیه شده باشد. برای مثال IdentityServer که در این سری بررسی خواهد شد و یا Azure Active Directory، نمونهای از IDPهایی هستند که این استاندارد را کاملا پیاده سازی کردهاند.
البته باید دقت داشت که این توکنهای دسترسی، تنها سطوح دسترسی به منابع API را مشخص میکنند و یا به عبارتی عملیات Authorization توسط آنها میسر میشود. عملیات ورود به سیستم اصطلاحا Authentication نام دارد و توسط استاندارد دیگری به نام OpenID Connect مدیریت میشود.
حرکت به سمت استاندارد OpenID Connect
OpenID Connect یک لایهی امنیتی بر فراز پروتکل OAuth 2.0 است که به اختصار به آن OIDC نیز گفته میشود. توسط آن یک کلاینت میتواند یک Identity token را علاوه بر Access token درخواست کند. از این Identity token برای ورود به برنامهی کلاینت استفاده میشود (Authentication) و پس از آن، برنامهی کلاینت بر اساس سطوح دسترسی تعریف شدهی در Access token، امکان دسترسی به امکانات مختلف یک API را خواهد یافت (Authorization). همچنین OpenID Connect امکان دسترسی به اطلاعات بیشتری از یک کاربر را نیز میسر میکند.
بنابراین OpenID Connect پروتکلی است که در عمل استفاده میشود و توسعه دهنده و جایگزین کنندهی پروتکل OAuth 2.0 میباشد. هرچند ممکن است در بسیاری از منابع صرفا به OAuth 2.0 بپردازند، اما در پشت صحنه با همان OpenID Connect کار میکنند.
مزیت دیگر کار با OpenID Connect، عدم الزام به استفادهی از API، در برنامهای خاص و یا قدیمی است. اگر برنامهی وب شما با هیچ نوع API ایی کار نمیکند، باز هم میتوانید از امکانات OpenID Connect بهرهمند شوید.
امن سازی برنامههای وب همواره چالش برانگیز بودهاست؛ خصوصا این روزها که نیاز است برنامهها، خارج از دیوارهای یک شرکت نیز در دسترس باشند و توسط انواع و اقسام وسایل ارتباطی مورد استفاده قرار گیرند. در سالهای قبل، عموما برنامههای thick clients مانند WPF و WinForms برای شرکتها توسعه داده میشدند و یا برنامههای وب مانند ASP.NET Web Forms که مبتنی بر سرویسها نبودند. در برنامههای ویندوزی، پس از لاگین شخص به شبکه و دومین داخلی شرکت، عموما از روش Windows Authentication برای مشخص سازی سطوح دسترسی کاربران استفاده میشود. در برنامههای Web Forms نیز بیشتر روش Forms Authentication برای اعتبارسنجی کاربران مرسوم است. امن سازی این نوع برنامهها سادهاست. عموما بر روی یک دومین ارائه میشوند و کوکیهای اعتبارسنجی کاربران برای ارائهی مباحثی مانند single sign-on (داشتن تنها یک صفحهی لاگین برای دسترسی به تمام برنامههای شرکت)، میسر است.
عصر شروع بهکارگیری سرویسهای وب
در سالهای اخیر این شیوهی کاری تغییر کرده و بیشتر بر اساس بکارگیری برنامههای مبتنی بر سرویسها شدهاست. برای مثال برای این مورد استاندارد WS-Security مربوط به WCF ارائه شدهاست که باز هم مرتبط است به برنامههای یک دومین و یا یک Application pool. اگر تعداد دومینها بیشتر شوند و نیاز به ارتباط امن بین آنها باشد، استاندارد SAML 2.0 مورد استفاده قرار میگرفت که هدف از آن، انتقال امن اعتبارسنجی و سطوح دسترسی کاربران بین دومینهای مختلف است. همانطور که ملاحظه میکنید تمام این برنامهها و استانداردها، داخل دیوارهای یک شرکت و یک دومین زندگی میکنند.
عصر شروع بهکارگیری Restful-API's
پس از آن باز هم شیوهی طراحی برنامههای تغییر کرد و شروع به ایجاد Restful-API's و HTTP API's کردیم. اینها دیگر الزاما داخل یک دومین ارائه نمیشوند و گاهی از اوقات حتی تحت کنترل ما هم نیستند. همچنین برنامههای ارائه شده نیز دیگر thick clients نیستند و ممکن است برنامههای سمت کلاینت Angular و یا حتی موبایل باشند که مستقیما با سرویسهای API برنامهها کار میکنند. حتی ممکن است یک API را طراحی کنیم که با یک API دیگر کار میکند.
در این حالت دیگر نمیتوان این APIها را با نگهداری آنها داخل دیوارهای یک شرکت محافظت کرد. اگر قرار است با یک HTTP API کار کنیم، این API باید عمومی باشد و در اینجا دیگر نمیتوان از روش Forms Authentication استفاده کرد. زیرا این روش اعتبارسنجی مختص برنامههای سمت سرور قرار گرفتهی در یک دومین طراحی شدهاست و آنچنان سازگاری با برنامههای سمت کلاینت و موبایل خارج از دیوارهای آن ندارد. همچنین ارسال نام کاربری و کلمهی عبور به ازای هر درخواست نیز روشی بسیار بدوی و نا امن است. اینجا است که عصر امن سازی برنامهها به کمک توکنها شروع میشود. با استفادهی از توکنها، بجای هر بار ارسال نام کاربری و کلمهی عبور به ازای هر درخواست از API، صرفا لیست سطوح دسترسی امضا شدهی به امکاناتی خاص، ارسال میشوند.
عصر شروع بهکارگیری Security Tokens
بنابراین در اینجا نیاز به برنامهای برای تولید توکنها و ارسال آنها به کلاینتها داریم. روش متداول پیاده سازی آن، ساخت یک برنامهی ابتدایی، برای دریافت نام کاربری و کلمهی عبور از کاربران و سپس بازگشت یک JSON Web Token به آنها است که بیانگر سطوح دسترسی آنها به قسمتهای مختلف برنامه است و کاربران باید این توکن را به ازای هر درخواستی به سمت سرور (بجای نام کاربری و کلمهی عبور و خود) ارسال کنند.
مشکل این روش در اینجا است که آن برنامهی خاص، باید از نام کاربری و کلمهی عبور کاربران مطلع باشد تا بتواند توکن مناسبی را برای آن کاربر خاص تولید کند. هر چند این روش برای یک تک برنامهی خاص بسیار مناسب به نظر میرسد، اما در یک شرکت، دهها برنامه مشغول به کارند و به اشتراک گذاری نام کاربری و کلمهی عبور کاربران، با تک تک آنها ایدهی مناسبی نیست و پس از مدتی از کنترل خارج خواهد شد. برای مثال کاربری در یک برنامه، کلمهی عبور خود را تغییر میدهد اما در برنامهای دیگر خیر و همین مسالهی عدم هماهنگی بین برنامهها و همچنین بخشهای مختلف یک شرکت، مدیریت یک دست برنامهها را تقریبا غیر ممکن میکند. همچنین در اینجا برنامههای ثالث را نیز باید در نظر داشت که آیا ضروری است آنها به ریز اطلاعات کاربران شرکت دسترسی پیدا کنند؟
به علاوه مشکل دیگر توسعهی این نوع برنامههای صدور توکن خانگی، اختراع مجدد چرخ است. در اینجا برای بهبود امنیت برنامه باید منقضی شدن، تمدید، امضای دیجیتال و اعتبارسنجی توکنها را خودمان پیاده سازی کنیم. توسعهی یک چنین سیستمی اگر غیرممکن نباشد، بسیار سخت و پیچیده است و همچنین باید باگهای امنیتی ممکن را نیز مدنظر داشت.
بنابراین تا اینجا به این نتیجه رسیدهایم که دیگر نمیخواهیم مدیریت نام کاربری و کلمهی عبور کاربران را در سطح هیچکدام از برنامههای خود انجام دهیم و هیچکدام از آنها قرار نیست دریافت کنندهی این اطلاعات باشند. قرار است این کار، به یک تک برنامهی مرکزی مخصوص اینکار منتقل شود و برای اینکار نیاز به پروتکلی امن است که بتوان این توکنهای تولیدی را ارسال و پردازش کرد.
حرکت به سمت «تامین کنندهی هویت مرکزی»
در گذشته، هر تک برنامهای دارای صفحهی لاگین و امکانات مدیریت کاربران آن، تغییر کلمهی عبور، تنظیم مجدد آن و اینگونه عملیات بود. اینروزها دیگر چنین کاری مرسوم نیست. این وظیفهی برنامهی شما نیست که بررسی کند کاربر وارد شدهی به سیستم کیست و آیا ادعای او صحیح است یا خیر؟ این نوع عملیات وظیفهی یک Identity provider و یا به اختصار IDP است. کار IDP اعتبارسنجی کاربر و در صورت نیاز، ارائهی اثبات هویت کاربر، به برنامهی درخواست کنندهاست.
در یک IDP عملیاتی مانند ثبت کاربران و مدیریت آنها انجام میشود. اینجا است که مفاهیمی مانند قفل کردن اکانت و یا تغییر کلمهی عبور و امثال آن انجام میشود و نه اینکه به ازای هر برنامهی تهیه شدهی برای یک شرکت، آن برنامه راسا اقدام به انجام چنین عملیاتی کند. به این ترتیب میتوان به امکان استفادهی مجدد از اطلاعات هویت کاربران و سطوح دسترسی آنها در بین تمام برنامههای موجود رسید.
همچنین با داشتن یک برنامهی IDP خوب پیاده سازی شده، از توزیع باگهای امنیتی مختلف در بین برنامههای گوناگون تهیه شده که هر کدام سیستم امنیتی خاص خودشان را دارند، جلوگیری خواهد شد. برای مثال فرض کنید میخواهید الگوریتم هش کردن پسوردهای سیستم را که امروز نا امن اعلام شدهاست، تغییر دهید. با داشتن یک IDP، دیگر نیازی نیست تا تمام برنامههای خود را برای رفع یک چنین باگهایی، تک تک تغییر دهید.
به علاوه این روزها روش استفادهی از نام کاربری و کلمهی عبور، تنها راه ورود به یک سیستم نیست و استفاده از کلیدهای دیجیتال و یا روشهای ویژهی ابزارهای موبایل نیز به این لیست اضافه شدهاند.
حرکت به سمت استاندارد OAuth 2
OAuth 2.0 پروتکلی است استاندارد، برای Authorization امن کاربران، توسط برنامههای وب، موبایل و دسکتاپ. به این ترتیب میتوان امکان دسترسی یک برنامه را به یک API، به نحوی استاندارد و امن میسر ساخت. OAuth 2.0 یک توکن دسترسی (Access token) را تولید میکند و در اختیار کلاینت قرار میدهد. سپس آن کلاینت با ارسال این توکن به API مدنظر، امکان دسترسی به امکانات مختلف آنرا خواهد یافت. به علاوه چون ماهیت برنامههای کلاینت وب و غیر وب متفاوت است، این استاندارد نحوهی دریافت چنین توکنی را برای برنامههای مختلف نیز تعریف میکند. به این ترتیب موارد مشترکی مانند تولید و نحوهی انتقال توکنها به کلاینتهای مختلف توسط این پروتکل استاندارد بیان میشود. در این حالت راهحلهای خانگی ما تبدیل به راهحلهایی میشوند که استاندارد OAuth 2.0 را پیاده سازی کرده باشند. بنابراین IDP ما باید بر مبنای این استاندارد تهیه شده باشد. برای مثال IdentityServer که در این سری بررسی خواهد شد و یا Azure Active Directory، نمونهای از IDPهایی هستند که این استاندارد را کاملا پیاده سازی کردهاند.
البته باید دقت داشت که این توکنهای دسترسی، تنها سطوح دسترسی به منابع API را مشخص میکنند و یا به عبارتی عملیات Authorization توسط آنها میسر میشود. عملیات ورود به سیستم اصطلاحا Authentication نام دارد و توسط استاندارد دیگری به نام OpenID Connect مدیریت میشود.
حرکت به سمت استاندارد OpenID Connect
OpenID Connect یک لایهی امنیتی بر فراز پروتکل OAuth 2.0 است که به اختصار به آن OIDC نیز گفته میشود. توسط آن یک کلاینت میتواند یک Identity token را علاوه بر Access token درخواست کند. از این Identity token برای ورود به برنامهی کلاینت استفاده میشود (Authentication) و پس از آن، برنامهی کلاینت بر اساس سطوح دسترسی تعریف شدهی در Access token، امکان دسترسی به امکانات مختلف یک API را خواهد یافت (Authorization). همچنین OpenID Connect امکان دسترسی به اطلاعات بیشتری از یک کاربر را نیز میسر میکند.
بنابراین OpenID Connect پروتکلی است که در عمل استفاده میشود و توسعه دهنده و جایگزین کنندهی پروتکل OAuth 2.0 میباشد. هرچند ممکن است در بسیاری از منابع صرفا به OAuth 2.0 بپردازند، اما در پشت صحنه با همان OpenID Connect کار میکنند.
مزیت دیگر کار با OpenID Connect، عدم الزام به استفادهی از API، در برنامهای خاص و یا قدیمی است. اگر برنامهی وب شما با هیچ نوع API ایی کار نمیکند، باز هم میتوانید از امکانات OpenID Connect بهرهمند شوید.
در Blazor 8x میتوان صفحات SSR ای را به همراه Blazor server islands و یا Blazor WASM islands داشت؛ یعنی یک کامپوننت Blazor Server که داخل یک صفحهی معمولی SSR قرار گرفته و با سرور، ارتباط SiganlR برقرار میکند و یا یک کامپوننت Blazor WASM که در قسمتی از صفحهی SSR درج شده و درون مرورگر کاربر اجرا میشود. به هر کدام از اینها یک «جزیرهی تعاملی» گفته میشود (interactive island). در این قسمت، نکات مرتبط با جزایر تعاملی Blazor Server را بررسی میکنیم.
بررسی یک مثال: تهیه یک برنامهی Blazor 8x برای نمایش لیست محصولات، به همراه جزئیات آنها
به لطف وجود SSR در Blazor 8x، میتوان HTML نهایی کامپوننتها و صفحات Blazor را همانند صفحات MVC و یا Razor pages، در سمت سرور تهیه و بازگشت داد. این خروجی در نهایت یک static HTML بیشتر نیست و گاهی از اوقات ما به بیش از یک خروجی ساده HTML ای نیاز داریم.
در این مثال که بر اساس قالب dotnet new blazor --interactivity Server تهیه میشود، قصد داریم موارد زیر را پیاده سازی کنیم:
- صفحهای که یک لیست محصولات فرضی را نمایش میدهد : بر اساس SSR
- صفحهای که جزئیات یک محصول را نمایش میدهد: بر اساس SSR
- دکمهای در ذیل قسمت نمایش جزئیات یک محصول، برای دریافت و نمایش لیست محصولات مشابه و مرتبط: بر اساس Blazor server islands
یعنی تا جائیکه ممکن است قصد نداریم تمام صفحات و تمام قسمتهای برنامه را با فعالسازی سراسری حالت تعاملی Blazor server که در قسمتهای قبل در مورد آن توضیح داده شد، پیاده سازی کنیم. میخواهیم فقط قسمت کوچکی از این سناریو را که واقعا نیاز به یک چنین قابلیتی را دارد، توسط یک جزیرهی تعاملی Blazor server واقع شدهی در قسمتی از یک صفحهی استاتیک SSR، مدیریت کنیم.
مدل برنامه: رکوردی برای ذخیره سازی اطلاعات یک محصول
در اینجا، هدف تعریف لیستی از محصولات فرضی است؛ به همراه خاصیتی که Id محصولات مشابه را نگهداری میکند (خاصیت Related).
سرویس برنامه: سرویسی برای بازگشت لیست محصولات
چون Blazor Server و SSR هر دو بر روی سرور اجرا میشوند، از لحاظ دسترسی به اطلاعات و کار با سرویسها، هماهنگی کاملی وجود داشته و میتوان کدهای یکسان و یکدستی را در اینجا بکار گرفت.
در ادامه کدهای کامل سرویس Services\ProductStore.cs را مشاهده میکنید:
هدف از این سرویس، ارائهی لیست تمام محصولات، دریافت اطلاعات یک محصول و همچنین یافتن لیست محصولات مشابه یک محصول خاص است.
این سرویس را باید در فایل Program.cs برنامه به صورت زیر معرفی کرد تا در فایلهای razor برنامهی جاری قابل دسترسی شود:
تکمیل صفحهی نمایش لیست محصولات
قصد داریم زمانیکه کاربر برای مثال به آدرس فرضی http://localhost:5136/products مراجعه کرد، با تصویر لیستی از محصولات مواجه شود:
کدهای این صفحه را که در فایل Components\Pages\Store\ProductsList.razor قرار میگیرند، در ادامه مشاهده میکنید:
توضیحات:
- جهت دسترسی به سرویس لیست محصولات، ابتدا سرویس IProductStore به این صفحه تزریق شدهاست.
- سپس در روال رویدادگردان آغازین OnInitializedAsync، کار دریافت اطلاعات و انتساب آن به لیستی، صورت گرفتهاست.
- در این متد جهت شبیه سازی یک عملیات async از یک Task.Delay استفاده شدهاست.
- چون این صفحه، یک صفحهی SSR عادی است، بدون تعریف ویژگی StreamRendering در آن، پس از اجرای برنامه، هیچگاه قسمت loading که در حالت products == null_ قرار است ظاهر شود، نمایش داده نمیشود؛ چون در این حالت (حذف نوع رندر)، صفحهی نهایی که به کاربر ارائه خواهد شد، یک صفحهی استاتیک کاملا رندر شدهی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحهی نهایی را دریافت و مشاهده کند. در حالت Streaming rendering، ابتدا میتوان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آنرا به محض آماده شدن در طی چند مرحله بازگشت داد.
- لینکهای نمایش داده شدهی در اینجا، به صفحهی ProductDetails اشاره میکنند که در آن، جزئیات محصول انتخابی نمایش داده میشوند.
تکمیل صفحهی نمایش جزئیات یک محصول
در صفحهی کامپوننت Components\Pages\Store\ProductDetails.razor، کار نمایش جزئیات محصول انتخابی صورت میگیرد:
توضیحات:
- باتوجه به نحوهی تعریف مسیریابی این صفحه، پارامتر ProductId از طریق آدرسی مانند http://localhost:5136/ProductDetails/1 دریافت میشود.
- سپس این ProductId را در روال رخدادگردان OnInitializedAsync، برای یافتن جزئیات محصول انتخابی از سرویس تزریقی IProductStore، بکار میگیریم.
- در اینجا نیز از Task.Delay برای شبیه سازی یک عملیات طولانی async مانند دریافت اطلاعات از یک بانک اطلاعاتی، کمک گرفته شدهاست.
- همچنین برای نمایش قسمت loading صفحه در حالت SSR، بازهم از StreamRendering استفاده کردهایم.
- اگر دقت کرده باشید، ذیل تصویر اطلاعات محصول، دکمهای نیز جهت بارگذاری اطلاعات محصولات مشابه، قرار دارد که ProductId محصول انتخابی را دریافت میکند:
بنابراین در ادامه کامپوننت RelatedProducts فوق را تکمیل میکنیم.
تکمیل کامپوننت نمایش لیست محصولات مشابه و مرتبط
در فایل Components\Pages\Store\RelatedProducts.razor، کار نمایش یک دکمه و سپس نمایش لیستی از محصولات مشابه، صورت میگیرد:
تعاملی کردن کامپوننت نمایش لیست محصولات مشابه
مشکل! اگر در این حالت برنامه را اجرا کرده و بر روی دکمهی related products کلیک کنیم، هیچ اتفاقی رخ نمیدهد! یعنی روال رویدادگران LoadRelatedProducts اصلا اجرا نمیشود. علت اینجا است که صفحات SSR، در نهایت یک static HTML بیشتر نیستند و فاقد قابلیتهای تعاملی، مانند واکنش نشان دادن به کلیک بر روی یک دکمه هستند.
محدودیتی که به همراه صفحات SSR وجود دارد این است: این نوع کامپوننتها و صفحات فقط یکبار رندر میشوند و نه بیشتر. بله میتوان بر روی آنها دهها دکمه، نوارهای لغزان، دراپداون و غیره را قرار داد، اما ... نمیتوان هیچگونه تعاملی را با آنها داشت. کامپوننت نهایی رندر شده و نمایش داده شده، دیگر در هیچجائی اجرا نمیشود. در این حالت است که میتوان تصمیم گرفت که نیاز است قسمتی از این صفحه، تعاملی شود.
به همین جهت باید نحوهی رندر کامپوننت RelatedProducts را به صورت یک جزیرهی تعاملی Blazor server درآورد تا رویداد منتسب به دکمهی related products موجود در آن، پردازش شود. بنابراین به صفحهی ProductDetails.razor مراجعه کرده و rendermode@ این کامپوننت را به صورت زیر به حالت InteractiveServer تغییر میدهیم:
اکنون اگر برنامه را مجددا اجرا کرده و بر روی دکمهی نمایش محصولات مشابه قرار گرفته در ذیل جزئیات یک محصول کلیک کنیم، بدون مشکل کار میکند:
نحوهی پردازش پشت صحنهی این نوع صفحات هم جالب است. برای اینکار به برگهی network مخصوص developer tools مرورگر مراجعه کرده و مراحل رسیدن به صفحهی نمایش جزئیات محصول را طی میکنیم:
- اگر دقت کنید، جابجایی بین صفحات، با استفاده از fetch انجام شده؛ یعنی با اینکه این صفحات در اصل static HTML خالص هستند، اما ... کار full reload صفحه مانند ASP.NET Web forms قدیمی انجام نمیشود (و یا حتی برنامههای MVC و Razor pages) و نمایش صفحات، Ajax ای است و با fetch استاندارد آن صورت میگیرد تا هنوز هم حس و حال SPA بودن برنامه حفظ شود. همچنین اطلاعات DOM کل صفحه را هم بهروز رسانی نمیکند؛ فقط موارد تغییر یافته در اینجا به روز رسانی خواهند شد.
این موارد توسط فایل blazor.web.js درج شدهی در کامپوننت آغازین App.razor، به صورت خودکار مدیریت میشوند:
به علاوه در این حالت ایجکسی fetch، کار دریافت مجدد فایلهای استاتیک مرتبط یک صفحه، مانند فایلهای js.، css.، تصاویر و غیره، مجددا انجام نمیشود که این مورد خود مزیتی است نسبت به حالت متداول برنامههای ASP.NET Core MVC و یا Razor pages. در حالت Blazor 8x SSR، فقط یک partial update از نوع Ajax ای انجام میشود.
به این قابلیت، enhanced navigation هم گفته میشود. برای مثال زمانیکه یک فرم SSR را در Blazor 8x به سمت سرور ارسال میکنیم، موقعیت scroll به صورت خودکار ذخیره و بازیابی میشود تا کاربر با یک full post back مواجه نشده و موقعیت جاری خود را در صفحه از دست ندهد (چنین ایدهای، یک زمانی در برنامههای ASP.NET Web forms هم برقرار بود و هست! به نظر مایکروسافت هنوز دلتنگ طراحی قدیمی ASP.NET Web forms است!).
- همچنین به محض نمایش صفحهی جزئیات محصول، پس از پایان کار نمایش آن، یک اتصال وبسوکت هم برقرار شده که مرتبط با جزیرهی تعاملی Blazor server تعریف شده، یا همان کامپوننت RelatedProducts است.
- یک disconnect را هم در اینجا مشاهده میکنید. اگر به یک صفحهی تعاملی مراجعه کنیم، همانطور که مشخص است، یک اتصال SignalR برقرار میشود (که به آن در اینجا circuit هم میگویند). اما اگر از این صفحه به سمت یک صفحهی SSR حرکت کنیم، پس از نمایش آن صفحه، اتصال SignalR قبلی که دیگر نیازی به آن نیست، بسته خواهد شد تا منابع سمت سرور، رها شوند.
در حین disconnect، شماره ID اتصال SignalR ای که دیگر به آن نیازی نیست، به برنامه ارسال میشود تا به صورت خودکار در سمت سرور بسته شود. تمام این موارد توسط blazor.web.js فریمورک، مدیریت میشوند.
در این تصویر ابتدا به آدرس http://localhost:5136/ProductDetails/1 مراجعه کردهایم که سبب برقراری اتصال یک وبسوکت شدهاست. سپس با کلیک بر روی دکمهی back، به صفحهی SSR مشاهدهی لیست محصولات برگشتهایم. در این حالت، دستور قطع اتصال SignalR قبلی صادر شدهاست.
نحوهی مدیریت Pre-rendering در جزایر تعاملی Blazor 8x
به صورت پیشفرض زمانیکه از حالت رندر InteractiveServer استفاده میکنیم، قابلیت pre-rendering آن نیز فعال است. یعنی ابتدا حداقل قالب و قسمتهای ثابت کامپوننت، در سمت سرور پردازش و رندر شده و سپس به سمت کلاینت ارسال میشوند. در این حالت کاربر، تجربهی کاربری روانتری را شاهد خواهد بود؛ چون برای مدتی نباید منتظر آماده شدن کل UI مرتبط باشد و حداقل، قسمتهایی از صفحه که تعاملی نیستند، قابل دسترسی و مشاهده هستند.
اگر به هر دلیلی نیاز به غیرفعال کردن این قابلیت را دارید، باید به صورت زیر عمل کرد:
در این حالت اگر برنامه را اجرا کنید، در حین نمایش صفحهی اصلی در برگیرندهی از نوع SSR، فقط جای این کامپوننت در صفحه مشخص میشود و پس از برقراری اتصال با سرور از طریق اتصال SignalR، شاهد UI کامپوننت RelatedProducts خواهیم بود، که نسبت به قبل، وقفهای را سبب خواهد شد.
نحوهی تعریف خواص استاتیک InteractiveServer بکار گرفته شده و یا کلاس InteractiveServerRenderMode را در ادامه مشاهده میکنید. جهت سهولت تعریف این موارد، سطر زیر که یک using static است، به فایل Imports.razor_ اضافه شدهاست:
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: Blazor8x-Server-Normal.zip
بررسی یک مثال: تهیه یک برنامهی Blazor 8x برای نمایش لیست محصولات، به همراه جزئیات آنها
به لطف وجود SSR در Blazor 8x، میتوان HTML نهایی کامپوننتها و صفحات Blazor را همانند صفحات MVC و یا Razor pages، در سمت سرور تهیه و بازگشت داد. این خروجی در نهایت یک static HTML بیشتر نیست و گاهی از اوقات ما به بیش از یک خروجی ساده HTML ای نیاز داریم.
در این مثال که بر اساس قالب dotnet new blazor --interactivity Server تهیه میشود، قصد داریم موارد زیر را پیاده سازی کنیم:
- صفحهای که یک لیست محصولات فرضی را نمایش میدهد : بر اساس SSR
- صفحهای که جزئیات یک محصول را نمایش میدهد: بر اساس SSR
- دکمهای در ذیل قسمت نمایش جزئیات یک محصول، برای دریافت و نمایش لیست محصولات مشابه و مرتبط: بر اساس Blazor server islands
یعنی تا جائیکه ممکن است قصد نداریم تمام صفحات و تمام قسمتهای برنامه را با فعالسازی سراسری حالت تعاملی Blazor server که در قسمتهای قبل در مورد آن توضیح داده شد، پیاده سازی کنیم. میخواهیم فقط قسمت کوچکی از این سناریو را که واقعا نیاز به یک چنین قابلیتی را دارد، توسط یک جزیرهی تعاملی Blazor server واقع شدهی در قسمتی از یک صفحهی استاتیک SSR، مدیریت کنیم.
مدل برنامه: رکوردی برای ذخیره سازی اطلاعات یک محصول
namespace BlazorDemoApp.Models; public record Product { public int Id { get; set; } public required string Title { get; set; } public required string Description { get; set; } public decimal Price { get; set; } public List<int> Related { get; set; } = new(); }
سرویس برنامه: سرویسی برای بازگشت لیست محصولات
چون Blazor Server و SSR هر دو بر روی سرور اجرا میشوند، از لحاظ دسترسی به اطلاعات و کار با سرویسها، هماهنگی کاملی وجود داشته و میتوان کدهای یکسان و یکدستی را در اینجا بکار گرفت.
در ادامه کدهای کامل سرویس Services\ProductStore.cs را مشاهده میکنید:
using BlazorDemoApp.Models; namespace BlazorDemoApp.Services; public interface IProductStore { IList<Product> GetAllProducts(); Product GetProduct(int id); IList<Product> GetRelatedProducts(int productId); } public class ProductStore : IProductStore { private static readonly List<Product> ProductsDataSource = new() { new Product { Id = 1, Title = "Smart speaker", Price = 22m, Description = "This smart speaker delivers excellent sound quality and comes with built-in voice control, offering an impressive music listening experience.", Related = new List<int> { 2, 3 }, }, new Product { Id = 2, Title = "Regular speaker", Price = 89m, Description = "Enjoy room-filling sound with this regular speaker. With its slick design, it perfectly fits into any room in your house.", Related = new List<int> { 1, 3 }, }, new Product { Id = 3, Title = "Speaker cable", Price = 12m, Description = "This high-quality speaker cable ensures a reliable and clear audio connection for your sound system.", }, }; public IList<Product> GetAllProducts() => ProductsDataSource; public Product GetProduct(int id) => ProductsDataSource.Single(p => p.Id == id); public IList<Product> GetRelatedProducts(int productId) { var product = ProductsDataSource.Single(x => x.Id == productId); return ProductsDataSource.Where(p => product.Related.Contains(p.Id)).ToList(); } }
این سرویس را باید در فایل Program.cs برنامه به صورت زیر معرفی کرد تا در فایلهای razor برنامهی جاری قابل دسترسی شود:
builder.Services.AddScoped<IProductStore, ProductStore>();
تکمیل صفحهی نمایش لیست محصولات
قصد داریم زمانیکه کاربر برای مثال به آدرس فرضی http://localhost:5136/products مراجعه کرد، با تصویر لیستی از محصولات مواجه شود:
کدهای این صفحه را که در فایل Components\Pages\Store\ProductsList.razor قرار میگیرند، در ادامه مشاهده میکنید:
@page "/Products" @using BlazorDemoApp.Models @using BlazorDemoApp.Services @inject IProductStore Store @attribute [StreamRendering] <h3>Products</h3> @if (_products == null) { <p>Loading...</p> } else { @foreach (var item in _products) { <a href="/ProductDetails/@item.Id"> <div> <div> <h5>@item.Title</h5> </div> <div> <h5>@item.Price.ToString("c")</h5> </div> </div> </a> } } @code { private IList<Product>? _products; protected override Task OnInitializedAsync() => GetProductsAsync(); private async Task GetProductsAsync() { await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering _products = Store.GetAllProducts(); } }
- جهت دسترسی به سرویس لیست محصولات، ابتدا سرویس IProductStore به این صفحه تزریق شدهاست.
- سپس در روال رویدادگردان آغازین OnInitializedAsync، کار دریافت اطلاعات و انتساب آن به لیستی، صورت گرفتهاست.
- در این متد جهت شبیه سازی یک عملیات async از یک Task.Delay استفاده شدهاست.
- چون این صفحه، یک صفحهی SSR عادی است، بدون تعریف ویژگی StreamRendering در آن، پس از اجرای برنامه، هیچگاه قسمت loading که در حالت products == null_ قرار است ظاهر شود، نمایش داده نمیشود؛ چون در این حالت (حذف نوع رندر)، صفحهی نهایی که به کاربر ارائه خواهد شد، یک صفحهی استاتیک کاملا رندر شدهی در سمت سرور است و کاربر باید تا زمان پایان این رندر در سمت سرور، منتظر بماند و سپس صفحهی نهایی را دریافت و مشاهده کند. در حالت Streaming rendering، ابتدا میتوان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آنرا به محض آماده شدن در طی چند مرحله بازگشت داد.
- لینکهای نمایش داده شدهی در اینجا، به صفحهی ProductDetails اشاره میکنند که در آن، جزئیات محصول انتخابی نمایش داده میشوند.
تکمیل صفحهی نمایش جزئیات یک محصول
در صفحهی کامپوننت Components\Pages\Store\ProductDetails.razor، کار نمایش جزئیات محصول انتخابی صورت میگیرد:
@page "/ProductDetails/{ProductId}" @using BlazorDemoApp.Models @using BlazorDemoApp.Services @inject IProductStore Store @attribute [StreamRendering] @if (_product == null) { <p>Loading...</p> } else { <div> <div> <h5> @_product.Title (@_product.Price.ToString("C")) </h5> <p> @_product.Description </p> </div> @if (_product.Related.Count > 0) { <div> <RelatedProducts ProductId="Convert.ToInt32(ProductId)" /> </div> } </div> <NavLink href="/Products">Back</NavLink> } @code { private Product? _product; [Parameter] public string? ProductId { get; set; } protected override Task OnInitializedAsync() => GetProductAsync(); private async Task GetProductAsync() { await Task.Delay(1000); // Simulates asynchronous loading to demonstrate streaming rendering _product = Store.GetProduct(Convert.ToInt32(ProductId)); } }
- باتوجه به نحوهی تعریف مسیریابی این صفحه، پارامتر ProductId از طریق آدرسی مانند http://localhost:5136/ProductDetails/1 دریافت میشود.
- سپس این ProductId را در روال رخدادگردان OnInitializedAsync، برای یافتن جزئیات محصول انتخابی از سرویس تزریقی IProductStore، بکار میگیریم.
- در اینجا نیز از Task.Delay برای شبیه سازی یک عملیات طولانی async مانند دریافت اطلاعات از یک بانک اطلاعاتی، کمک گرفته شدهاست.
- همچنین برای نمایش قسمت loading صفحه در حالت SSR، بازهم از StreamRendering استفاده کردهایم.
- اگر دقت کرده باشید، ذیل تصویر اطلاعات محصول، دکمهای نیز جهت بارگذاری اطلاعات محصولات مشابه، قرار دارد که ProductId محصول انتخابی را دریافت میکند:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" />
تکمیل کامپوننت نمایش لیست محصولات مشابه و مرتبط
در فایل Components\Pages\Store\RelatedProducts.razor، کار نمایش یک دکمه و سپس نمایش لیستی از محصولات مشابه، صورت میگیرد:
@using BlazorDemoApp.Models @using BlazorDemoApp.Services @inject IProductStore Store <button @onclick="LoadRelatedProducts">Related products</button> @if (_loadRelatedProducts) { @if (_relatedProducts == null) { <p>Loading...</p> } else { <div> @foreach (var item in _relatedProducts) { <a href="/ProductDetails/@item.Id"> <div> <h5>@item.Title (@item.Price.ToString("C"))</h5> </div> </a> } </div> } } @code{ private IList<Product>? _relatedProducts; private bool _loadRelatedProducts; [Parameter] public int ProductId { get; set; } private async Task LoadRelatedProducts() { _loadRelatedProducts = true; await Task.Delay(1000); // Simulates asynchronous loading to demonstrate InteractiveServer mode _relatedProducts = Store.GetRelatedProducts(ProductId); } }
تعاملی کردن کامپوننت نمایش لیست محصولات مشابه
مشکل! اگر در این حالت برنامه را اجرا کرده و بر روی دکمهی related products کلیک کنیم، هیچ اتفاقی رخ نمیدهد! یعنی روال رویدادگران LoadRelatedProducts اصلا اجرا نمیشود. علت اینجا است که صفحات SSR، در نهایت یک static HTML بیشتر نیستند و فاقد قابلیتهای تعاملی، مانند واکنش نشان دادن به کلیک بر روی یک دکمه هستند.
محدودیتی که به همراه صفحات SSR وجود دارد این است: این نوع کامپوننتها و صفحات فقط یکبار رندر میشوند و نه بیشتر. بله میتوان بر روی آنها دهها دکمه، نوارهای لغزان، دراپداون و غیره را قرار داد، اما ... نمیتوان هیچگونه تعاملی را با آنها داشت. کامپوننت نهایی رندر شده و نمایش داده شده، دیگر در هیچجائی اجرا نمیشود. در این حالت است که میتوان تصمیم گرفت که نیاز است قسمتی از این صفحه، تعاملی شود.
به همین جهت باید نحوهی رندر کامپوننت RelatedProducts را به صورت یک جزیرهی تعاملی Blazor server درآورد تا رویداد منتسب به دکمهی related products موجود در آن، پردازش شود. بنابراین به صفحهی ProductDetails.razor مراجعه کرده و rendermode@ این کامپوننت را به صورت زیر به حالت InteractiveServer تغییر میدهیم:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@InteractiveServer"/>
نحوهی پردازش پشت صحنهی این نوع صفحات هم جالب است. برای اینکار به برگهی network مخصوص developer tools مرورگر مراجعه کرده و مراحل رسیدن به صفحهی نمایش جزئیات محصول را طی میکنیم:
- اگر دقت کنید، جابجایی بین صفحات، با استفاده از fetch انجام شده؛ یعنی با اینکه این صفحات در اصل static HTML خالص هستند، اما ... کار full reload صفحه مانند ASP.NET Web forms قدیمی انجام نمیشود (و یا حتی برنامههای MVC و Razor pages) و نمایش صفحات، Ajax ای است و با fetch استاندارد آن صورت میگیرد تا هنوز هم حس و حال SPA بودن برنامه حفظ شود. همچنین اطلاعات DOM کل صفحه را هم بهروز رسانی نمیکند؛ فقط موارد تغییر یافته در اینجا به روز رسانی خواهند شد.
این موارد توسط فایل blazor.web.js درج شدهی در کامپوننت آغازین App.razor، به صورت خودکار مدیریت میشوند:
<script src="_framework/blazor.web.js"></script>
به علاوه در این حالت ایجکسی fetch، کار دریافت مجدد فایلهای استاتیک مرتبط یک صفحه، مانند فایلهای js.، css.، تصاویر و غیره، مجددا انجام نمیشود که این مورد خود مزیتی است نسبت به حالت متداول برنامههای ASP.NET Core MVC و یا Razor pages. در حالت Blazor 8x SSR، فقط یک partial update از نوع Ajax ای انجام میشود.
به این قابلیت، enhanced navigation هم گفته میشود. برای مثال زمانیکه یک فرم SSR را در Blazor 8x به سمت سرور ارسال میکنیم، موقعیت scroll به صورت خودکار ذخیره و بازیابی میشود تا کاربر با یک full post back مواجه نشده و موقعیت جاری خود را در صفحه از دست ندهد (چنین ایدهای، یک زمانی در برنامههای ASP.NET Web forms هم برقرار بود و هست! به نظر مایکروسافت هنوز دلتنگ طراحی قدیمی ASP.NET Web forms است!).
- همچنین به محض نمایش صفحهی جزئیات محصول، پس از پایان کار نمایش آن، یک اتصال وبسوکت هم برقرار شده که مرتبط با جزیرهی تعاملی Blazor server تعریف شده، یا همان کامپوننت RelatedProducts است.
- یک disconnect را هم در اینجا مشاهده میکنید. اگر به یک صفحهی تعاملی مراجعه کنیم، همانطور که مشخص است، یک اتصال SignalR برقرار میشود (که به آن در اینجا circuit هم میگویند). اما اگر از این صفحه به سمت یک صفحهی SSR حرکت کنیم، پس از نمایش آن صفحه، اتصال SignalR قبلی که دیگر نیازی به آن نیست، بسته خواهد شد تا منابع سمت سرور، رها شوند.
در حین disconnect، شماره ID اتصال SignalR ای که دیگر به آن نیازی نیست، به برنامه ارسال میشود تا به صورت خودکار در سمت سرور بسته شود. تمام این موارد توسط blazor.web.js فریمورک، مدیریت میشوند.
در این تصویر ابتدا به آدرس http://localhost:5136/ProductDetails/1 مراجعه کردهایم که سبب برقراری اتصال یک وبسوکت شدهاست. سپس با کلیک بر روی دکمهی back، به صفحهی SSR مشاهدهی لیست محصولات برگشتهایم. در این حالت، دستور قطع اتصال SignalR قبلی صادر شدهاست.
نحوهی مدیریت Pre-rendering در جزایر تعاملی Blazor 8x
به صورت پیشفرض زمانیکه از حالت رندر InteractiveServer استفاده میکنیم، قابلیت pre-rendering آن نیز فعال است. یعنی ابتدا حداقل قالب و قسمتهای ثابت کامپوننت، در سمت سرور پردازش و رندر شده و سپس به سمت کلاینت ارسال میشوند. در این حالت کاربر، تجربهی کاربری روانتری را شاهد خواهد بود؛ چون برای مدتی نباید منتظر آماده شدن کل UI مرتبط باشد و حداقل، قسمتهایی از صفحه که تعاملی نیستند، قابل دسترسی و مشاهده هستند.
اگر به هر دلیلی نیاز به غیرفعال کردن این قابلیت را دارید، باید به صورت زیر عمل کرد:
<RelatedProducts ProductId="Convert.ToInt32(ProductId)" @rendermode="@(new InteractiveServerRenderMode(false))"/>
نحوهی تعریف خواص استاتیک InteractiveServer بکار گرفته شده و یا کلاس InteractiveServerRenderMode را در ادامه مشاهده میکنید. جهت سهولت تعریف این موارد، سطر زیر که یک using static است، به فایل Imports.razor_ اضافه شدهاست:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
public static class RenderMode { public static InteractiveServerRenderMode InteractiveServer { get; } = new InteractiveServerRenderMode(); public static InteractiveWebAssemblyRenderMode InteractiveWebAssembly { get; } = new InteractiveWebAssemblyRenderMode(); public static InteractiveAutoRenderMode InteractiveAuto { get; } = new InteractiveAutoRenderMode(); } public class InteractiveServerRenderMode : IComponentRenderMode { public InteractiveServerRenderMode() : this(true) { } public InteractiveServerRenderMode(bool prerender) => this.Prerender = prerender; public bool Prerender { get; } }
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: Blazor8x-Server-Normal.zip
نظرات اشتراکها
چه موقع باید از Angular یک یا دو استفاده کرد؟
با توجه به نهایی نشدن نسخه 2، به نظر شما پروژه ای که قراره تا 3 ماه آینده کامل و راه اندازی شه، از کدوم نخسه استفاده بشه بهتره؟
- اگر از نسخه 1 استفاده کنم، به نظرم منطقی نیست و ارتقا به 2 کلی هزینه داره
- اگر که کلا استفاده نکنم، در آینده باید استفاده کنم که تغییرش کلی هزینه داره
- اگر از نسخه 2 استفاده کنم، میتونم تا 3 ماه آینده که نهایی میشه در صورتی که تغییری نیاز بود اعمال کنم
مطالب
نوروز مبارک!
میگن لحظه تحویل سال نو، مشغول به هر کاری که باشی تا پایان سال سرت به همان گرم خواهد بود. به همین جهت سال نو رو با یک سری ویدیوی رایگان ASP.NET MVC 3 که به سفارش و هزینه مایکروسافت توسط Pluralsight تهیه شده، شروع میکنیم. باشد تا پایان سال به همین منوال ادامه یابد!
سال نو مبارک!