قالبهای پیشفرض Blazor 8x، به همراه قسمت بازنویسی شدهی ASP.NET Core Identity برای Blazor هم هستند که در این قسمت به بررسی نحوهی عملکرد آنها میپردازیم.
معرفی قالبهای جدید شروع پروژههای Blazor در دات نت 8 به همراه قسمت Identity
در
قسمت دوم این سری، با قالبهای جدید شروع پروژههای Blazor 8x آشنا شدیم و هدف ما در آنجا بیشتر بررسی حالتهای مختلف رندر Blazor در دات نت 8 بود. تمام این قالبها به همراه یک سوئیچ دیگر هم به نام auth-- هستند که توسط آن و با مقدار دهی Individual که به معنای Individual accounts است، میتوان کدهای پیشفرض و ابتدایی Identity UI جدید را نیز به قالب در حال ایجاد، به صورت خودکار اضافه کرد؛ یعنی به صورت زیر:
اجرای قسمتهای تعاملی برنامه بر روی سرور؛ به همراه کدهای Identity:
dotnet new blazor --interactivity Server --auth Individual
اجرای قسمتهای تعاملی برنامه در مرورگر، توسط فناوری وباسمبلی؛ به همراه کدهای Identity:
dotnet new blazor --interactivity WebAssembly --auth Individual
برای اجرای قسمتهای تعاملی برنامه، ابتدا حالت Server فعالسازی میشود تا فایلهای WebAssembly دریافت شوند، سپس فقط از WebAssembly استفاده میکند؛ به همراه کدهای Identity:
dotnet new blazor --interactivity Auto --auth Individual
فقط از حالت SSR یا همان static server rendering استفاده میشود (این نوع برنامهها تعاملی نیستند)؛ به همراه کدهای Identity:
dotnet new blazor --interactivity None --auth Individual
و یا توسط پرچم all-interactive--، که interactive render mode را در ریشهی برنامه قرار میدهد؛ به همراه کدهای Identity:
dotnet new blazor --all-interactive --auth Individual
یک نکته: بانک اطلاعاتی پیشفرض مورد استفادهی در این نوع پروژهها، SQLite است. برای تغییر آن میتوانید از پرچم use-local-db-- هم استفاده کنید تا از LocalDB بجای SQLite استفاده کند.
Identity UI جدید Blazor در دات نت 8، یک بازنویسی کامل است
در نگارشهای قبلی Blazor هم امکان افزودن Identity UI، به پروژههای Blazor وجود داشت (
اطلاعات بیشتر)، اما ... آن پیاده سازی، کاملا مبتنی بر Razor pages بود. یعنی کاربر، برای کار با آن مجبور بود از فضای برای مثال Blazor Server خارج شده و وارد فضای جدید ASP.NET Core شود تا بتواند، Identity UI نوشته شدهی با صفحات cshtml. را اجرا کند و به اجزای آن دسترسی پیدا کند (یعنی عملا آن قسمت UI اعتبارسنجی، Blazor ای نبود) و پس از اینکار، مجددا به سمت برنامهی Blazor هدایت شود؛ اما ... این مشکل در دات نت 8 با ارائهی صفحات SSR برطرف شدهاست.
همانطور که در
قسمت قبل نیز بررسی کردیم، صفحات SSR، طول عمر کوتاهی دارند و هدف آنها تنها ارسال یک HTML استاتیک به مرورگر کاربر است؛ اما ... دسترسی کاملی را به HttpContext برنامهی سمت سرور دارند و این دقیقا چیزی است که زیر ساخت Identity، برای کار و تامین کوکیهای مورد نیاز خودش، احتیاج دارد. صفحات Identity UI از یک طرف از HttpContext برای نوشتن کوکی لاگین موفقیت آمیز در مرورگر کاربر استفاده میکنند (در این سیستم، هرجائی متدهای XyzSignInAsync وجود دارد، در پشت صحنه و در پایان کار، سبب درج یک کوکی اعتبارسنجی و احراز هویت در مرورگر کاربر نیز خواهد شد) و از طرف دیگر با استفاده از میانافزارهای اعتبارسنجی و احراز هویت ASP.NET Core، این کوکیها را به صورت خودکار پردازش کرده و از آنها جهت ساخت خودکار شیء User قابل دسترسی در این صفحات (شیء context.User.Identity@)، استفاده میکنند.
به همین جهت تمام صفحات Identity UI ارائه شده در Blazor 8x، از نوع SSR هستند و اگر در آنها از فرمی استفاده شده، دقیقا همان فرمهای تعاملی است که در
قسمت چهارم این سری بررسی کردیم. یعنی صرفا بخاطر داشتن یک فرم، ضرورتی به استفادهی از جزایر تعاملی Blazor Server و یا Blazor WASM را ندیدهاند و اینگونه فرمها را بر اساس مدل جدید فرمهای تعاملی Blazor 8x پیاده سازی کردهاند. بنابراین این صفحات کاملا یکدست هستند و از ابتدا تا انتها، به صورت یکپارچه بر اساس مدل SSR کار میکنند (که در اصل خیلی شبیه به Razor pages یا Viewهای MVC هستند) و جزیره، جزیرهای، طراحی نشدهاند.
روش دسترسی به HttpContext در صفحات SSR
اگر به کدهای Identity UI قالب آغازین یک پروژه که در ابتدای بحث روش تولید آنها بیان شد، مراجعه کنید، استفاده از یک چنین «
پارامترهای آبشاری» را میتوان مشاهده کرد:
@code {
[CascadingParameter]
public HttpContext HttpContext { get; set; } = default!;
متد ()AddRazorComponents ای که در فایل Program.cs اضافه میشود،
کار ثبت CascadingParameter ویژهی فوق را به صورت زیر انجام میدهد که در Blazor 8x به آن یک Root-level cascading value میگویند:
services.TryAddCascadingValue(sp => sp.GetRequiredService<EndpointHtmlRenderer>().HttpContext);
مقادیر آبشاری برای ارسال اطلاعاتی بین درختی از کامپوننتها، از یک والد به چندین فرزند که چندین لایه ذیل آن واقع شدهاند، مفید است. برای مثال فرض کنید میخواهید اطلاعات عمومی تنظیمات یک کاربر را به چندین زیر کامپوننت، ارسال کنید. یک روش آن، ارسال شیء آن به صورت پارامتر، به تک تک آنها است و راه دیگر، تعریف یک CascadingParameter است؛ شبیه به کاری که در اینجا انجام شدهاست تا دیگر نیازی نباشد تا تک تک زیر کامپوننتها این شیء را به یک لایه زیریرین خود، یکی یکی منتقل کنند.
در کدهای Identity UI ارائه شده، از این CascadingParameter برای مثال جهت خروج از برنامه (HttpContext.SignOutAsync) و یا دسترسی به اطلاعات هدرهای رسید (if (HttpMethods.IsGet(HttpContext.Request.Method)))، دسترسی به سرویسها (()<HttpContext.Features.Get<ITrackingConsentFeature)، تامین مقدار Cancellation Token به کمک HttpContext.RequestAborted و یا حتی
در صفحهی جدید Error.razor که آن نیز یک صفحهی SSR است، برای دریافت HttpContext?.TraceIdentifier استفادهی مستقیم شدهاست.
نکتهی مهم: باید بهخاطر داشت که فقط و فقط در صفحات SSR است که میتوان به HttpContext به نحو آبشاری فوق دسترسی یافت و همانطور که در
قسمت قبل نیز بررسی شد، سایر حالات رندر Blazor از دسترسی به آن، پشتیبانی نمیکنند و اگر چنین پارامتری را تنظیم کردید، نال دریافت میکنید.
بررسی تفاوتهای تنظیمات ابتدایی قالب جدید Identity UI در Blazor 8x با نگارشهای قبلی آن
مطالب و نکات مرتبط با قالب قبلی را در مطلب «
Blazor 5x - قسمت 22 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 2 - ورود به سیستم و خروج از آن» میتوانید مشاهده کنید.
در
قسمت سوم این سری، روش ارتقاء یک برنامهی قدیمی Blazor Server را به نگارش 8x آن بررسی کردیم و یکی از تغییرات مهم آن، حذف فایلهای cshtml. ای آغاز برنامه و انتقال وظایف آن، به فایل جدید App.razor و انتقال مسیریاب Blazor به فایل جدید Routes.razor است که پیشتر در فایل App.razor نگارشهای قبلی Blazor وجود داشت.
در این نگارش جدید، محتوای فایل Routes.razor به همراه قالب Identity UI به صورت زیر است:
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
</Router>
در نگارشهای قبلی، این مسیریاب داخل کامپوننت CascadingAuthenticationState محصور میشد تا توسط آن بتوان AuthenticationState را به تمام کامپوننتهای برنامه ارسال کرد. به این ترتیب برای مثال کامپوننت AuthorizeView، شروع به کار میکند و یا توسط شیء context.User میتوان به User claims دسترسی یافت و یا به کمک ویژگی [Authorize]، دسترسی به صفحهای را محدود به کاربران اعتبارسنجی شده کرد.
اما ... در اینجا با یک نگارش ساده شده سروکار داریم؛ از این جهت که برنامههای جدید، به همراه جزایر تعاملی هم میتوانند باشند و باید بتوان این AuthenticationState را به آنها هم ارسال کرد. به همین جهت مفهوم جدید مقادیر آبشاری سطح ریشه (Root-level cascading values) که پیشتر در این بحث معرفی شد، در اینجا برای معرفی AuthenticationState استفاده شدهاست.
در اینجا کامپوننت جدید FocusOnNavigate را هم مشاهده میکنید. با استفاده از این کامپوننت و براساس CSS Selector معرفی شده، پس از هدایت به یک صفحهی جدید، این المان مشخص شده دارای focus خواهد شد. هدف از آن، اطلاع رسانی به screen readerها در مورد هدایت به صفحهای دیگر است (برای مثال، کمک به نابیناها برای درک بهتر وضعیت جاری).
همچنین در اینجا المان NotFound را هم مشاهده نمیکنید. این المان فقط در برنامههای WASM جهت سازگاری با نگارشهای قبلی، هنوز هم قابل استفادهاست. جایگزین آنرا در
قسمت سوم این سری، برای برنامههای Blazor server در بخش «ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده» آن بررسی کردیم.
روش انتقال اطلاعات سراسری اعتبارسنجی یک کاربر به کامپوننتها و جزایر تعاملی واقع در صفحات SSR
مشکل! زمانیکه از ترکیب صفحات SSR و جزایر تعاملی واقع در آن استفاده میکنیم ... جزایر واقع در آنها دیگر این مقادیر آبشاری را دریافت نمیکنند و این مقادیر در آنها نال است. برای حل این مشکل در Blazor 8x، باید مقادیر آبشاری سطح ریشه را (Root-level cascading values) به صورت زیر در فایل Program.cs برنامه ثبت کرد:
builder.Services.AddCascadingValue(sp =>new Preferences { Theme ="Dark" });
پس از این تغییر، اکنون نه فقط صفحات SSR، بلکه جزایر واقع در آنها نیز توسط ویژگی [CascadingParameter] میتوانند به این مقدار آبشاری، دسترسی داشته باشند. به این ترتیب است که در برنامههای Blazor، کامپوننتهای تعاملی هم دسترسی به اطلاعات شخص لاگین شدهی توسط صفحات SSR را دارند. برای مثال اگر به فایل Program.cs قالب جدید Identity UI عنوان شده مراجعه کنید، چنین سطوری در آن قابل مشاهده هستند
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
متد جدید AddCascadingAuthenticationState، فقط کار ثبت AuthenticationStateProvider برنامه را به صورت آبشاری انجام میدهد.
این AuthenticationStateProvider سفارشی جدید هم در فایل اختصاصی IdentityRevalidatingAuthenticationStateProvider.cs پوشهی Identity قالب پروژههای جدید Blazor 8x که به همراه اعتبارسنجی هستند، قابل مشاهدهاست.
یا اگر به قالبهای پروژههای WASM دار مراجعه کنید، تعریف به این صورت تغییر کردهاست؛ اما مفهوم آن یکی است:
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingServerAuthenticationStateProvider>();
در این پروژهها، یک AuthenticationStateProvider سفارشی دیگری در فایل PersistingServerAuthenticationStateProvider.cs ارائه شدهاست (این فایلها، جزو استاندارد قالبهای جدید Identity UI پروژههای Blazor 8x هستند؛ فقط کافی است، یک نمونه پروژهی آزمایشی را با سوئیچ جدید auth Individual-- ایجاد کنید، تا بتوانید این فایلهای یاد شده را مشاهده نمائید).
AuthenticationStateProviderهای سفارشی مانند کلاسهای IdentityRevalidatingAuthenticationStateProvider و PersistingServerAuthenticationStateProvider که در این قالبها موجود هستند، چون به صورت آبشاری کار تامین AuthenticationState را انجام میدهند، به این ترتیب میتوان به شیء context.User.Identity@ در جزایر تعاملی نیز به سادگی دسترسی داشت.
یعنی به صورت خلاصه، یکبار قرارداد AuthenticationStateProvider عمومی (بدون هیچ نوع پیاده سازی) به صورت یک Root-level cascading value ثبت میشود تا در کل برنامه قابل دسترسی شود. سپس یک پیاده سازی اختصاصی از آن توسط ()<builder.Services.AddScoped<AuthenticationStateProvider, XyzProvider به برنامه معرفی میشود تا آنرا تامین کند. به این ترتیب زیر ساخت امن سازی صفحات با استفاده از ویژگی attribute [Authorize]@ و یا دسترسی به اطلاعات کاربر جاری با استفاده از شیء context.User@ و یا امکان استفاده از کامپوننت AuthorizeView برای نمایش اطلاعاتی ویژه به کاربران اعتبارسنجی شده مانند صفحهی Auth.razor زیر، مهیا میشود:
@page "/auth"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize]
<PageTitle>Auth</PageTitle>
<h1>You are authenticated</h1>
<AuthorizeView>
Hello @context.User.Identity?.Name!
</AuthorizeView>
سؤال: چگونه یک پروژهی سمت سرور، اطلاعات اعتبارسنجی خودش را با یک پروژهی WASM سمت کلاینت به اشتراک میگذارد (برای مثال در حالت رندر Auto)؟ این انتقال اطلاعات باتوجه به یکسان نبودن محل اجرای این دو پروژه (یکی بر روی کامپیوتر سرور و دیگری بر روی مرورگر کلاینت، در کامپیوتری دیگر) چگونه انجام میشود؟ این مورد را در قسمت بعد، با معرفی
PersistentComponentState و «فیلدهای مخفی» مرتبط با آن، بررسی میکنیم.