Build a Complete Blog App with Blazor Server - Step-by-Step Tutorial from Scratch by Abhay Prince
You will learn about fundamentals of Blazor Server, Routing, Shared Reusable Components, Custom Authentication, EF Core Code First, Passing data from parent to child components and vice versa, Passing events from child to parent components, Forms validations and submissions, styling components, using javascript with blazor. third party components integration with blazor, blazor lifecycle events, authentication and authorization in blazor server, programtically navigating between component pages, multiple routes paths for single component page, dynamic routes and getting data from routes, authorized view and much more...
اشتراکها
Here’s a summary of what’s new in this preview release:
- Improved ASP.NET Core debugging experience
- Servers & middleware
-
IHttpSysRequestTimingFeature
- SNI hostname in
ITlsHandshakeFeature
-
IExceptionHandler
-
- Blazor
- New Blazor Web App project template
- Blazor router integration with endpoint routing
- Enable interactivity for individual components with Blazor Server
- Improved packaging of Webcil files
- Blazor Content Security Policy (CSP) compatibility
- API authoring
- Support for generic attributes
- SignalR
- SignalR seamless reconnect
- Native AOT
- Support for
AsParameters
and automatic metadata generation in compile-timed generated minimal APIs
- Support for
- Authentication and authorization
- Authentication updates in ASP.NET Core SPA templates
- New analyzer for recommended
AuthorizationBuilder
usage
Spawn an Online Game with Blazor, .NET 7 and Clean Architecture in under 60 minutes
In this fun talk, Luke Parker will show you step-by-step how to build a playable game in under 60 minutes. He will go over some basic game design, project planning, then build the game and finally… play live with the audience! Utilizing code sharing and the magic of Clean Architecture allows for very rapid app development. Also, leveraging MudBlazor as the component library lets you save time and to focus on what really matters.
یک نکتهی تکمیلی: امکان اجرای دوباره OnInitialized{Async} در برنامههای Blazor Server
در برنامههای Blazor Server اگر حالت رندر شدن به Server Prerendered تنظیم شده باشد، کامپوننت ابتدا یکبار به صورت استاتیک رندر میشود و زمانیکه اتصال SignalR برقرار میشود، بار دیگر نیز رندر خواهد شد (هدف این است که پیش از دریافت کامل برنامهی Blazor بتوان محتوایی را نمایش داد). بنابراین در این حالت متد رویدادگردان OnInitialized{Async} حتما دوبار اجرا میشود (ماخذ).
اگر میخواهید این رفتار را تغییر دهید، به فایل Host.cshtml_ برنامههای Blazor Server مراجعه کرده و "render-mode="ServerPrerendered پیشفرض را به "render-mode="Server تغییر دهید؛ یا کدهای OnInit را به OnAfterRenderAsync انتقال داده و وضعیت وجود اتصال SignalR را بررسی کنید:
@page "/" @using Microsoft.JSInterop @inject IComponentContext ComponentContext @inject IJSRuntime jsRuntime <p>Navigate to the counter component.</p> @code{ protected override async Task OnAfterRenderAsync(bool firstRender) { if (!ComponentContext.IsConnected) return; UriHelper.NavigateTo("/counter"); } }
اشتراکها
بومی سازی در MVC 6
پس از آشنایی با دو مدل هاستینگ برنامههای Blazor در قسمت قبل، اکنون میخواهیم ساختار ابتدایی قالبهای این دو پروژه را بررسی کنیم.
ایجاد پروژههای خالی Blazor
در انتهای قسمت قبل، با روش ایجاد پروژههای خالی Blazor به کمک NET SDK 5x. آشنا شدیم. به همین جهت دو پوشهی جدید BlazorWasmSample و BlazorServerSample را ایجاد کرده و از طریق خط فرمان و با کمک NET CLI.، در پوشهی اولی دستور dotnet new blazorwasm و در پوشهی دومی دستور dotnet new blazorserver را اجرا میکنیم.
البته اجرای این دو دستور، نیاز به اتصال به اینترنت را هم برای بار اول دارند؛ تا فایلهای مورد نیاز و بستههای مرتبط را دریافت و restore کنند. بسته به سرعت اینترنت، حداقل یک ربعی را باید صبر کنید تا دریافت ابتدایی بستههای مرتبط به پایان برسد. برای دفعات بعدی، از کش محلی NuGet، برای restore بستههای blazor استفاده میشود که بسیار سریع است.
بررسی ساختار پروژهی Blazor Server و اجرای آن
پس از اجرای دستور dotnet new blazorserver در یک پوشهی خالی و ایجاد پروژهی ابتدایی آن:
همانطور که مشاهده میکنید، ساختار این پروژه، بسیار شبیه به یک پروژهی استاندارد ASP.NET Core از نوع Razor pages است.
- در پوشهی properties آن، فایل launchSettings.json قرار دارد که برای نمونه، شماره پورت اجرایی برنامه را در حالت اجرای توسط دستور dotnet run و یا توسط IIS Express مشخص میکند.
- پوشهی wwwroot آن، مخصوص ارائهی فایلهای ایستا مانند wwwroot\css\bootstrap است. در ابتدای کار، این پوشه به همراه فایلهای CSS بوت استرپ است. در ادامه اگر نیاز باشد، فایلهای جاوا اسکریپتی را نیز میتوان به این قسمت اضافه کرد.
- در پوشهی Data آن، سرویس تامین اطلاعاتی اتفاقی قرار دارد؛ به نام WeatherForecastService که هدف آن، تامین اطلاعات یک جدول نمایشی است که در ادامه در آدرس https://localhost:5001/fetchdata قابل مشاهده است.
- در پوشهی Pages، تمام کامپوننتهای Razor برنامه قرار میگیرند. یکی از مهمترین صفحات آن، فایل Pages\_Host.cshtml است. کار این صفحهی ریشه، افزودن تمام فایلهای CSS و JS، به برنامهاست. بنابراین در آینده نیز از همین صفحه برای افزودن فایلهای CSS و JS استفاده خواهیم کرد. اگر به قسمت body این صفحه دقت کنیم، تگ جدید کامپوننت قابل مشاهدهاست:
کار آن، رندر کامپوننت App.razor واقع در ریشهی پروژهاست.
همچنین در همینجا، تگهای دیگری نیز قابل مشاهده هستند:
همانطور که در قسمت قبل نیز عنوان شد، اتصال برنامهی در حال اجرای در مرورگر blazor server با backend، از طریق یک اتصال دائم SignalR صورت میگیرد. اگر در این بین خطای اتصالی رخ دهد، div با id مساوی blazor-error-ui فوق، یکی از پیامهایی را که مشاهده میکنید، بسته به اینکه برنامه در حالت توسعه در حال اجرا است و یا در حالت ارائهی نهایی، نمایش میدهد. افزودن مدخل blazor.server.js نیز به همین منظور است. کار آن مدیریت اتصال دائم SignalR، به صورت خودکار است و از این لحاظ نیازی به کدنویسی خاصی از سمت ما ندارد. این اتصال، کار به روز رسانی UI و هدایت رخدادها را به سمت سرور، انجام میدهد.
- در پوشهی Shared، یکسری فایلهای اشتراکی قرار دارند که قرار است در کامپوننتهای واقع در پوشهی Pages مورد استفاه قرار گیرند. برای نمونه فایل Shared\MainLayout.razor، شبیه به master page برنامههای web forms است که قالب کلی سایت را مشخص میکند. داخل آن Body@ را مشاهده میکنید که به معنای نمایش صفحات دیگر، دقیقا در همین محل است. همچنین در این پوشه فایل Shared\NavMenu.razor نیز قرار دارد که ارجاعی به آن در MainLayout.razor ذکر شده و کار آن نمایش منوی آبیرنگ سمت چپ صفحهاست.
- در پوشهی ریشهی برنامه، فایل Imports.razor_ قابل مشاهدهاست. مزیت تعریف usingها در اینجا این است که از تکرار آنها در کامپوننتهای razor ای که در ادامه تهیه خواهیم کرد، جلوگیری میکند. هر using تعریف شدهی در اینجا، در تمام کامپوننتها، قابل دسترسی است؛ به آن global imports هم گفته میشود.
- در پوشهی ریشهی برنامه، فایل App.razor نیز قابل مشاهدهاست. کار آن تعریف قالب پیشفرض برنامهاست که برای مثال به Shared\MainLayout.razor اشاره میکند. همچنین کامپوننت مسیریابی نیز در اینجا ذکر شدهاست:
اگر شخصی مسیر از پیش تعریف شدهای را وارد کند، به قسمت Found وارد میشود و در غیر اینصورت به قسمت NotFound. در قسمت Found است که از قالب MainLayout برای رندر یک کامپوننت توسط کامپوننت RouteView، استفاده خواهد شد و در قسمت NotFound، فقط پیام «Sorry, there's nothing at this address» به کاربر نمایش داده میشود. قالبهای هر دو نیز قابل تغییر و سفارشی سازی هستند.
- فایل appsettings.json نیز همانند برنامههای استاندارد ASP.NET Core در اینجا مشاهده میشود.
- فایل Program.cs آن که نقطهی آغازین برنامهاست و کار فراخوانی Startup.cs را انجام میدهد، دقیقا با یک فایل Program.cs برنامهی استاندارد ASP.NET Core یکی است.
- در فایل Startup.cs آن، همانند قبل دو متد تنظیم سرویسها و تنظیم میانافزارها قابل مشاهدهاست.
در ConfigureServices آن، سرویسهای صفحات razor و ServerSideBlazor اضافه شدهاند. همچنین سرویس نمونهی WeatherForecastService نیز در اینجا ثبت شدهاست.
قسمتهای جدید متد Configure آن، ثبت مسیریابی توکار BlazorHub است که مرتبط است با اتصال دائم SignalR برنامه و اگر مسیری پیدا نشد، به Host_ هدایت میشود:
در ادامه در همان پوشه، دستور dotnet run را اجرا میکنیم تا پروژه، کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.
که به همراه 13 درخواست و نزدیک به 600 KB دریافت اطلاعات از سمت سرور است.
این برنامهی نمونه، به همراه سه صفحهی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شدهاست. اگر صفحهی شمارشگر آنرا باز کنیم، با کلیک بر روی دکمهی آن، هرچند مقدار current count افزایش مییابد، اما شاهد post-back متداولی به سمت سرور نیستیم و این صفحه بسیار شبیه به صفحات برنامههای SPA (تک صفحهای وب) به نظر میرسد:
همانطور که عنوان شد، مدخل blazor.server.js فایل Pages\_Host.cshtml، کار به روز رسانی UI و هدایت رخدادها را به سمت سرور به صورت خودکار انجام میدهد. به همین جهت است که post-back ای را مشاهده نمیکنیم و برنامه، شبیه به یک برنامهی SPA به نظر میرسد؛ هر چند تمام رندرهای آن سمت سرور انجام میشوند و توسط SignalR به سمت کلاینت بازگشت داده خواهند شد.
برای نمونه اگر بر روی دکمهی شمارشگر کلیک کنیم، در برگهی network مرورگر، هیچ اثری از آن مشاهده نمیشود (هیچ رفت و برگشتی را مشاهده نمیکنیم). علت اینجا است که اطلاعات متناظر با این کلیک، از طریق web socket باز شدهی توسط SignalR، به سمت سرور ارسال شده و نتیجهی واکنش به این کلیکها و رندر HTML نهایی سمت سرور آن، از همین طریق به سمت کلاینت بازگشت داده میشود.
بررسی ساختار پروژهی Blazor WASM و اجرای آن
پس از اجرای دستور dotnet new blazorwasm در یک پوشهی خالی و ایجاد پروژهی ابتدایی آن:
همان صفحات پروژهی خالی Blazor Server در اینجا نیز قابل مشاهده هستند. این برنامهی نمونه، به همراه سه صفحهی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شدهاست. صفحات و کامپوننتهای پوشههای Pages و Shared نیز دقیقا همانند پروژهی Blazor Server قابل مشاهده هستند. مفاهیمی مانند فایلهای Imports.razor_ و App.razor نیز مانند قبل هستند.
البته در اینجا فایل Startup ای مشاهده نمیشود و تمام تنظیمات آغازین برنامه، داخل فایل Program.cs انجام خواهند شد:
در اینجا ابتدا کامپوننت App.razor را به برنامه معرفی میکند که ساختار آن با نمونهی مشابه Blazor Server دقیقا یکی است. سپس سرویسی را برای دسترسی به HttpClient، به سیستم تزریق وابستگیهای برنامه معرفی میکند. هدف از آن، دسترسی سادهتر به endpointهای یک ASP.NET Core Web API است. از این جهت که در یک برنامهی سمت کلاینت، دیگر دسترسی مستقیمی به سرویسهای سمت سرور را نداریم و برای کار با آنها همانند سایر برنامههای SPA که از Ajax استفاده میکنند، در اینجا از HttpClient برای کار با Web APIهای مختلف استفاده میشود.
تفاوت ساختاری دیگر این پروژهی WASM، با نمونهی Blazor Server، ساختار پوشهی wwwroot آن است:
که به همراه فایل جدید نمونهی wwwroot\sample-data\weather.json است؛ بجای سرویس متناظر سمت سرور آن در برنامهی blazor server. همچنین فایل جدید wwwroot\index.html نیز قابل مشاهدهاست و محتوای تگ body آن به صورت زیر است:
- چون این برنامه، یک برنامهی سمت کلاینت است، اینبار بجای فایل Host_ سمت سرور، فایل index.html سمت کلاینت را برای ارائهی آغازین برنامه داریم که وابستگی به دات نت ندارد و توسط مرورگر قابل درک است.
- در ابتدای بارگذاری برنامه، یک loading نمایش داده میشود که در اینجا نحوهی تعریف آن مشخص است. همچنین اگر خطایی رخ دهد نیز توسط div ای با id مساوی blazor-error-ui اطلاع رسانی میشود.
- مدخل blazor.webassembly.js در اینجا، کار دریافت وب اسمبلی و فایلهای NET runtime. را انجام میدهد؛ برخلاف برنامههای Blazor Server که توسط فایل blazor.server.js، یک ارتباط دائم SignalR را با سرور برقرار میکردند تا کدهای رندر شدهی سمت سرور را دریافت و نمایش دهند و یا اطلاعاتی را به سمت سرور ارسال کنند: برای مثال بر روی دکمهای کلیک شدهاست، اطلاعات مربوطه را در سمت سرور پردازش کن و نتیجهی نهایی رندر شده را بازگشت بده. اما در اینجا همه چیز داخل مرورگر اجرا میشود و برای این نوع اعمال، رفت و برگشتی به سمت سرور صورت نمیگیرد. به همین جهت تمام کدهای #C ما به سمت کلاینت ارسال شده و داخل مرورگر به کمک فناوری وب اسمبلی، اجرا میشوند. در اینجا از لحاظ ارسال تمام کدهای مرتبط با UI برنامهی سمت کلاینت به مرورگر کاربر، تفاوتی با فریمورکهایی مانند Angular و یا React نیست و آنها هم تمام کدهای UI برنامه را کامپایل کرده و یکجا ارسال میکنند.
در ادامه در همان پوشه، دستور dotnet run را اجرا میکنیم تا پروژه کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.
که به همراه 205 درخواست و نزدیک به 9.6 MB دریافت اطلاعات از سمت سرور است. البته اگر همین صفحه را refresh کنیم، دیگر شاهد دریافت مجدد فایلهای DLL مربوط به NET Runtime. نخواهیم بود و اینبار از کش مرورگر خوانده میشوند:
در این برنامهی سمت کلاینت، ابتدا تمام فایلهای NET Runtime. و وب اسمبلی دریافت شده و سپس اجرای تغییرات UI، در همین سمت و بدون نیاز به اتصال دائم SignalR ای به سمت سرور، پردازش و نمایش داده میشوند. به همین جهت زمانیکه بر روی دکمهی شمارشگر آن کلیک میکنیم، اتفاقی در برگهی network مرورگر ثبت نمیشود و رفت و برگشتی به سمت سرور صورت نمیگیرد.
عدم وجود اتصال SignalR، مزیت امکان اجرای آفلاین برنامهی WASM را نیز میسر میکند. برای مثال یکبار دیگر همان برنامهی Blazor Server را به کمک دستور dotnet run اجرا کنید. سپس آنرا در مرورگر در آدرس https://localhost:5001 باز کنید. اکنون پنجرهی کنسولی که dotnet run را اجرا کرده، خاتمه دهید (قسمت اجرای سمت سرور آنرا ببندید).
بلافاصله تصویر «سعی در اتصال مجدد» فوق را مشاهده خواهیم کرد که به دلیل قطع اتصال SignalR رخ دادهاست. یعنی یک برنامهی Blazor Server، بدون این اتصال دائم، قادر به ادامهی فعالیت نیست. اما چنین محدودیتی با برنامههای Blazor WASM وجود ندارد.
البته بدیهی است اگر یک Web API سمت سرور برای ارائهی اطلاعاتی به برنامهی WASM نیاز باشد، API سمت سرور برنامه نیز باید جهت کار و نمایش اطلاعات در سمت کلاینت در دسترس باشد؛ وگرنه قسمتهای متناظر با آن، قادر به نمایش و یا ثبت اطلاعات نخواهند بود.
ایجاد پروژههای خالی Blazor
در انتهای قسمت قبل، با روش ایجاد پروژههای خالی Blazor به کمک NET SDK 5x. آشنا شدیم. به همین جهت دو پوشهی جدید BlazorWasmSample و BlazorServerSample را ایجاد کرده و از طریق خط فرمان و با کمک NET CLI.، در پوشهی اولی دستور dotnet new blazorwasm و در پوشهی دومی دستور dotnet new blazorserver را اجرا میکنیم.
البته اجرای این دو دستور، نیاز به اتصال به اینترنت را هم برای بار اول دارند؛ تا فایلهای مورد نیاز و بستههای مرتبط را دریافت و restore کنند. بسته به سرعت اینترنت، حداقل یک ربعی را باید صبر کنید تا دریافت ابتدایی بستههای مرتبط به پایان برسد. برای دفعات بعدی، از کش محلی NuGet، برای restore بستههای blazor استفاده میشود که بسیار سریع است.
بررسی ساختار پروژهی Blazor Server و اجرای آن
پس از اجرای دستور dotnet new blazorserver در یک پوشهی خالی و ایجاد پروژهی ابتدایی آن:
همانطور که مشاهده میکنید، ساختار این پروژه، بسیار شبیه به یک پروژهی استاندارد ASP.NET Core از نوع Razor pages است.
- در پوشهی properties آن، فایل launchSettings.json قرار دارد که برای نمونه، شماره پورت اجرایی برنامه را در حالت اجرای توسط دستور dotnet run و یا توسط IIS Express مشخص میکند.
- پوشهی wwwroot آن، مخصوص ارائهی فایلهای ایستا مانند wwwroot\css\bootstrap است. در ابتدای کار، این پوشه به همراه فایلهای CSS بوت استرپ است. در ادامه اگر نیاز باشد، فایلهای جاوا اسکریپتی را نیز میتوان به این قسمت اضافه کرد.
- در پوشهی Data آن، سرویس تامین اطلاعاتی اتفاقی قرار دارد؛ به نام WeatherForecastService که هدف آن، تامین اطلاعات یک جدول نمایشی است که در ادامه در آدرس https://localhost:5001/fetchdata قابل مشاهده است.
- در پوشهی Pages، تمام کامپوننتهای Razor برنامه قرار میگیرند. یکی از مهمترین صفحات آن، فایل Pages\_Host.cshtml است. کار این صفحهی ریشه، افزودن تمام فایلهای CSS و JS، به برنامهاست. بنابراین در آینده نیز از همین صفحه برای افزودن فایلهای CSS و JS استفاده خواهیم کرد. اگر به قسمت body این صفحه دقت کنیم، تگ جدید کامپوننت قابل مشاهدهاست:
<body> <component type="typeof(App)" render-mode="ServerPrerendered" />
همچنین در همینجا، تگهای دیگری نیز قابل مشاهده هستند:
<body> <component type="typeof(App)" render-mode="ServerPrerendered" /> <div id="blazor-error-ui"> <environment include="Staging,Production"> An error has occurred. This application may no longer respond until reloaded. </environment> <environment include="Development"> An unhandled exception has occurred. See browser dev tools for details. </environment> <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> <script src="_framework/blazor.server.js"></script> </body>
- در پوشهی Shared، یکسری فایلهای اشتراکی قرار دارند که قرار است در کامپوننتهای واقع در پوشهی Pages مورد استفاه قرار گیرند. برای نمونه فایل Shared\MainLayout.razor، شبیه به master page برنامههای web forms است که قالب کلی سایت را مشخص میکند. داخل آن Body@ را مشاهده میکنید که به معنای نمایش صفحات دیگر، دقیقا در همین محل است. همچنین در این پوشه فایل Shared\NavMenu.razor نیز قرار دارد که ارجاعی به آن در MainLayout.razor ذکر شده و کار آن نمایش منوی آبیرنگ سمت چپ صفحهاست.
- در پوشهی ریشهی برنامه، فایل Imports.razor_ قابل مشاهدهاست. مزیت تعریف usingها در اینجا این است که از تکرار آنها در کامپوننتهای razor ای که در ادامه تهیه خواهیم کرد، جلوگیری میکند. هر using تعریف شدهی در اینجا، در تمام کامپوننتها، قابل دسترسی است؛ به آن global imports هم گفته میشود.
- در پوشهی ریشهی برنامه، فایل App.razor نیز قابل مشاهدهاست. کار آن تعریف قالب پیشفرض برنامهاست که برای مثال به Shared\MainLayout.razor اشاره میکند. همچنین کامپوننت مسیریابی نیز در اینجا ذکر شدهاست:
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> </Found> <NotFound> <LayoutView Layout="@typeof(MainLayout)"> <p>Sorry, there's nothing at this address.</p> </LayoutView> </NotFound> </Router>
- فایل appsettings.json نیز همانند برنامههای استاندارد ASP.NET Core در اینجا مشاهده میشود.
- فایل Program.cs آن که نقطهی آغازین برنامهاست و کار فراخوانی Startup.cs را انجام میدهد، دقیقا با یک فایل Program.cs برنامهی استاندارد ASP.NET Core یکی است.
- در فایل Startup.cs آن، همانند قبل دو متد تنظیم سرویسها و تنظیم میانافزارها قابل مشاهدهاست.
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton<WeatherForecastService>(); }
قسمتهای جدید متد Configure آن، ثبت مسیریابی توکار BlazorHub است که مرتبط است با اتصال دائم SignalR برنامه و اگر مسیری پیدا نشد، به Host_ هدایت میشود:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); }
که به همراه 13 درخواست و نزدیک به 600 KB دریافت اطلاعات از سمت سرور است.
این برنامهی نمونه، به همراه سه صفحهی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شدهاست. اگر صفحهی شمارشگر آنرا باز کنیم، با کلیک بر روی دکمهی آن، هرچند مقدار current count افزایش مییابد، اما شاهد post-back متداولی به سمت سرور نیستیم و این صفحه بسیار شبیه به صفحات برنامههای SPA (تک صفحهای وب) به نظر میرسد:
همانطور که عنوان شد، مدخل blazor.server.js فایل Pages\_Host.cshtml، کار به روز رسانی UI و هدایت رخدادها را به سمت سرور به صورت خودکار انجام میدهد. به همین جهت است که post-back ای را مشاهده نمیکنیم و برنامه، شبیه به یک برنامهی SPA به نظر میرسد؛ هر چند تمام رندرهای آن سمت سرور انجام میشوند و توسط SignalR به سمت کلاینت بازگشت داده خواهند شد.
برای نمونه اگر بر روی دکمهی شمارشگر کلیک کنیم، در برگهی network مرورگر، هیچ اثری از آن مشاهده نمیشود (هیچ رفت و برگشتی را مشاهده نمیکنیم). علت اینجا است که اطلاعات متناظر با این کلیک، از طریق web socket باز شدهی توسط SignalR، به سمت سرور ارسال شده و نتیجهی واکنش به این کلیکها و رندر HTML نهایی سمت سرور آن، از همین طریق به سمت کلاینت بازگشت داده میشود.
بررسی ساختار پروژهی Blazor WASM و اجرای آن
پس از اجرای دستور dotnet new blazorwasm در یک پوشهی خالی و ایجاد پروژهی ابتدایی آن:
همان صفحات پروژهی خالی Blazor Server در اینجا نیز قابل مشاهده هستند. این برنامهی نمونه، به همراه سه صفحهی نمایش Home، نمایش یک شمارشگر و نمایش اطلاعاتی از پیش آماده شدهاست. صفحات و کامپوننتهای پوشههای Pages و Shared نیز دقیقا همانند پروژهی Blazor Server قابل مشاهده هستند. مفاهیمی مانند فایلهای Imports.razor_ و App.razor نیز مانند قبل هستند.
البته در اینجا فایل Startup ای مشاهده نمیشود و تمام تنظیمات آغازین برنامه، داخل فایل Program.cs انجام خواهند شد:
namespace BlazorWasmSample { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); await builder.Build().RunAsync(); } } }
تفاوت ساختاری دیگر این پروژهی WASM، با نمونهی Blazor Server، ساختار پوشهی wwwroot آن است:
که به همراه فایل جدید نمونهی wwwroot\sample-data\weather.json است؛ بجای سرویس متناظر سمت سرور آن در برنامهی blazor server. همچنین فایل جدید wwwroot\index.html نیز قابل مشاهدهاست و محتوای تگ body آن به صورت زیر است:
<body> <div id="app">Loading...</div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> <script src="_framework/blazor.webassembly.js"></script> </body>
- در ابتدای بارگذاری برنامه، یک loading نمایش داده میشود که در اینجا نحوهی تعریف آن مشخص است. همچنین اگر خطایی رخ دهد نیز توسط div ای با id مساوی blazor-error-ui اطلاع رسانی میشود.
- مدخل blazor.webassembly.js در اینجا، کار دریافت وب اسمبلی و فایلهای NET runtime. را انجام میدهد؛ برخلاف برنامههای Blazor Server که توسط فایل blazor.server.js، یک ارتباط دائم SignalR را با سرور برقرار میکردند تا کدهای رندر شدهی سمت سرور را دریافت و نمایش دهند و یا اطلاعاتی را به سمت سرور ارسال کنند: برای مثال بر روی دکمهای کلیک شدهاست، اطلاعات مربوطه را در سمت سرور پردازش کن و نتیجهی نهایی رندر شده را بازگشت بده. اما در اینجا همه چیز داخل مرورگر اجرا میشود و برای این نوع اعمال، رفت و برگشتی به سمت سرور صورت نمیگیرد. به همین جهت تمام کدهای #C ما به سمت کلاینت ارسال شده و داخل مرورگر به کمک فناوری وب اسمبلی، اجرا میشوند. در اینجا از لحاظ ارسال تمام کدهای مرتبط با UI برنامهی سمت کلاینت به مرورگر کاربر، تفاوتی با فریمورکهایی مانند Angular و یا React نیست و آنها هم تمام کدهای UI برنامه را کامپایل کرده و یکجا ارسال میکنند.
در ادامه در همان پوشه، دستور dotnet run را اجرا میکنیم تا پروژه کامپایل و همچنین وب سرور آن نیز اجرا شده و برنامه در آدرس https://localhost:5001 قابل دسترسی شود.
که به همراه 205 درخواست و نزدیک به 9.6 MB دریافت اطلاعات از سمت سرور است. البته اگر همین صفحه را refresh کنیم، دیگر شاهد دریافت مجدد فایلهای DLL مربوط به NET Runtime. نخواهیم بود و اینبار از کش مرورگر خوانده میشوند:
در این برنامهی سمت کلاینت، ابتدا تمام فایلهای NET Runtime. و وب اسمبلی دریافت شده و سپس اجرای تغییرات UI، در همین سمت و بدون نیاز به اتصال دائم SignalR ای به سمت سرور، پردازش و نمایش داده میشوند. به همین جهت زمانیکه بر روی دکمهی شمارشگر آن کلیک میکنیم، اتفاقی در برگهی network مرورگر ثبت نمیشود و رفت و برگشتی به سمت سرور صورت نمیگیرد.
عدم وجود اتصال SignalR، مزیت امکان اجرای آفلاین برنامهی WASM را نیز میسر میکند. برای مثال یکبار دیگر همان برنامهی Blazor Server را به کمک دستور dotnet run اجرا کنید. سپس آنرا در مرورگر در آدرس https://localhost:5001 باز کنید. اکنون پنجرهی کنسولی که dotnet run را اجرا کرده، خاتمه دهید (قسمت اجرای سمت سرور آنرا ببندید).
بلافاصله تصویر «سعی در اتصال مجدد» فوق را مشاهده خواهیم کرد که به دلیل قطع اتصال SignalR رخ دادهاست. یعنی یک برنامهی Blazor Server، بدون این اتصال دائم، قادر به ادامهی فعالیت نیست. اما چنین محدودیتی با برنامههای Blazor WASM وجود ندارد.
البته بدیهی است اگر یک Web API سمت سرور برای ارائهی اطلاعاتی به برنامهی WASM نیاز باشد، API سمت سرور برنامه نیز باید جهت کار و نمایش اطلاعات در سمت کلاینت در دسترس باشد؛ وگرنه قسمتهای متناظر با آن، قادر به نمایش و یا ثبت اطلاعات نخواهند بود.
مطالب
MVC Scaffolding #1
پیشنیازها
کل سری ASP.NET MVC
به همراه کل سری EF Code First
MVC Scaffolding چیست؟
MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامههای ASP.NET MVC مبتنی بر EF Code First.
بررسی مقدماتی MVC Scaffolding
امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژههای ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندیها و قابلیتهای سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدلهای برنامه اضافه کنید:
سپس بر روی پوشه Controllers کلیک راست کرده و گزینه Add controller را انتخاب کنید. تنظیمات صفحه ظاهر شده را مطابق شکل زیر تغییر دهید:
همانطور که ملاحظه میکنید در قسمت قالبها، تولید کنترلرهایی با اکشن متدهای ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آنرا پذیرفتهایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:
الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدلهای برنامه ایجاد میگردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه میکند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلیهای دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)
معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شدهای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی میتواند شامل فیلدهای بیشتری باشد و نباید آنرا مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).
مزیتها:
قسمت عمدهای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شدهاند. کارهای اولیهای که با هر روش و الگوی شناخته شدهای قصد پیاده سازی آنها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمدهای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرمهای آنرا بخواهید با jQuery Ajax پیاده سازی کنید یا کنترلهای پیش فرض را با افزونههای jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.
نصب بسته اصلی MVC Scaffolding توسط NuGet
بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console میتوان به پروژه خود اضافه کرد:
اگر به مراحل نصب آن دقت کنید یک سری وابستگی را نیز به صورت خودکار دریافت کرده و نصب میکند:
از مواردی که با T4 آغاز شدهاند در قسمتهای بعدی برای سفارشی سازی کدهای تولیدی استفاده خواهیم کرد.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز میتوان پیاده سازی کرد:
نوشتن این دستور نیز ساده است. حروف sca را تایپ کرده و دکمه tab را فشار دهید. منویی ظاهر خواهد شد که امکان انتخاب دستور Scaffold را میدهد. یا برای نوشتن Controller نیز به همین نحو میتوان عمل کرد.
نکته و مزیت مهم دیگری که در اینجا در دسترس میباشد، سوئیچهای خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچهای قابل استفاده است.
برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
اگر این دستور را اجرا کنیم به همان نتیجه حاصل از مراحل توضیح داده شده قبل خواهیم رسید؛ البته یا یک تفاوت: یک Partial View اضافهتر نیز به نام CreateOrEdit در پوشه Views\Tasks ایجاد شده است. این Partial View بر اساس بازخورد برنامه نویسها مبنی بر اینکه Viewهای Edit و Create بسیار شبیه به هم هستند، ایجاد شده است.
بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding
در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
البته اگر دستور فوق را به همین نحو اجرا کنید با یک سری خطای Skipping مواجه خواهید شد مبنی بر اینکه فایلهای قبلی موجود هستند و این دستور قصد بازنویسی آنها را ندارد. برای اجبار به تولید مجدد کدهای موجود میتوان از سوئیچ Force استفاده کرد:
اتفاقی که در اینجا رخ خواهد داد، بازنویسی کد بیکیفت ابتدایی همراه با وهله سازی مستقیم DbContext در کنترلر، به نمونه بهتری که از الگوی مخزن استفاده میکند میباشد:
کیفیت کد تولیدی جدید مبتنی بر الگوی مخزن بد نیست؛ دقیقا همانی است که در هزاران سایت اینترنتی تبلیغ میشود؛ اما ... آنچنان مناسب هم نیست و اشکالات زیر را به همراه دارد:
اگر به ITaskRepository تولیدی دقت کنیم دارای خروجی IQueryable است؛ به این حالت leaky abstraction گفته میشود. زیرا امکان تغییر کلی یک خروجی IQueryable در لایههای دیگر برنامه وجود دارد و حد و مرز سیستم توسط آن مشخص نخواهد شد. بهتر است خروجیهای لایه سرویس یا لایه مخزن در اینجا از نوعهای IList یا IEnumerable باشند که درون آنها از IQueryableها برای پیاده سازی منطق مورد نظر کمک گرفته شده است.
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزنهای تعریف شده را تک موجودیتی میکند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیتها به کلاسهای مخزن واگذار شدهاند و کنترلرها به این نحو خلوتتر گردیدهاند، یک مرحله پیشرفت محسوب میشود.
کل سری ASP.NET MVC
به همراه کل سری EF Code First
MVC Scaffolding چیست؟
MVC Scaffolding ابزاری است برای تولید خودکار کدهای «اولیه» برنامه، جهت بالا بردن سرعت تولید برنامههای ASP.NET MVC مبتنی بر EF Code First.
بررسی مقدماتی MVC Scaffolding
امکان اجرای ابزار MVC Scaffolding از دو طریق دستورات خط فرمان Powershell و یا صفحه دیالوگ افزودن یک کنترلر در پروژههای ASP.NET MVC وجود دارد. در ابتدا حالت ساده و ابتدایی استفاده از صفحه دیالوگ افزودن یک کنترلر را بررسی خواهیم کرد تا با کلیات این فرآیند آشنا شویم. سپس در ادامه به خط فرمان Powershell که اصل توانمندیها و قابلیتهای سفارشی MVC Scaffolding در آن قرار دارد، خواهیم پرداخت.
برای این منظور یک پروژه جدید MVC را آغاز کنید؛ ابزارهای مقدماتی MVC Scaffolding از اولین به روز رسانی ASP.NET MVC3 به بعد با VS.NET یکپارچه هستند.
ابتدا کلاس زیر را به پوشه مدلهای برنامه اضافه کنید:
using System; using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace MvcApplication1.Models { public class Task { public int Id { set; get; } [Required] public string Name { set; get; } [DisplayName("Due Date")] public DateTime? DueDate { set; get; } [DisplayName("Is Complete")] public bool IsComplete { set; get; } [StringLength(450)] public string Description { set; get; } } }
همانطور که ملاحظه میکنید در قسمت قالبها، تولید کنترلرهایی با اکشن متدهای ثبت و نمایش اطلاعات مبتنی بر EF Code First انتخاب شده است. کلاس مدل نیز به کلاس Task فوق تنظیم گردیده و در زمان انتخاب DbContext مرتبط، گزینه new data context را انتخاب کرده و نام پیش فرض آنرا پذیرفتهایم. زمانیکه بر روی دکمه Add کلیک کنیم، اتفاقات ذیل رخ خواهند داد:
الف) کنترلر جدید TasksController.cs به همراه تمام کدهای Insert/Update/Delete/Display مرتبط تولید خواهد شد.
ب) کلاس DbContext خودکاری به نام MvcApplication1Context.cs در پوشه مدلهای برنامه ایجاد میگردد تا کلاس Task را در معرض دید EF Code first قرار دهد. (همانطور که عنوان شد یکی از پیشنیازهای بحث Scaffolding آشنایی با EF Code first است)
ج) در پوشه Views\Tasks، پنج View جدید را جهت مدیریت فرآیندهای نمایش صفحات Insert، حذف، ویرایش، نمایش و غیره تهیه میکند.
د) فایل وب کانفیگ برنامه جهت درج رشته اتصالی به بانک اطلاعاتی تغییر کرده است. حالت پیش فرض آن استفاده از SQL CE است و برای استفاده از آن نیاز است قسمت 15 سری EF سایت جاری را پیشتر مطالعه کرده باشید (به چه اسمبلیهای دیگری مانند System.Data.SqlServerCe.dll برای اجرا نیاز است و چطور باید اتصال به بانک اطلاعاتی را تنظیم کرد)
معایب:
کیفیت کد تولیدی پیش فرض قابل قبول نیست:
- DbContext در سطح یک کنترلر وهله سازی شده و الگوی Context Per Request در اینجا بکارگرفته نشده است. واقعیت یک برنامه ASP.NET MVC کامل، داشتن چندین Partial View تغدیه شونده از کنترلرهای مختلف در یک صفحه واحد است. اگر قرار باشد به ازای هر کدام یکبار DbContext وهله سازی شود یعنی به ازای هر صفحه چندین بار اتصال به بانک اطلاعاتی باید برقرار شود که سربار زیادی را به همراه دارد. (قسمت 12 سری EF سایت جاری)
- اکشن متدها حاوی منطق پیاده سازی اعمال CRUD یا همان Create/Update/Delete هستند. به عبارتی از یک لایه سرویس برای خلوت کردن اکشن متدها استفاده نشده است.
- از ViewModel تعریف شدهای به نام Task هم به عنوان Domain model و هم ViewModel استفاده شده است. یک کلاس متناظر با جداول بانک اطلاعاتی میتواند شامل فیلدهای بیشتری باشد و نباید آنرا مستقیما در معرض دید یک View قرار داد (خصوصا از لحاظ مسایل امنیتی).
مزیتها:
قسمت عمدهای از کارهای «اولیه» تهیه یک کنترلر و همچنین Viewهای مرتبط به صورت خودکار انجام شدهاند. کارهای اولیهای که با هر روش و الگوی شناخته شدهای قصد پیاده سازی آنها را داشته باشید، وقت زیادی را به خود اختصاص داده و نهایتا آنچنان تفاوت عمدهای هم با کدهای تولیدی در اینجا نخواهند داشت. حداکثر فرمهای آنرا بخواهید با jQuery Ajax پیاده سازی کنید یا کنترلهای پیش فرض را با افزونههای jQuery غنی سازی نمائید. اما شروع کار و کدهای اولیه چیزی بیشتر از این نیست.
نصب بسته اصلی MVC Scaffolding توسط NuGet
بسته اصلی MVC Scaffolding را با استفاده از دستور خط فرمان Powershell ذیل، از طریق منوی Tools، گزینه Library package manager و انتخاب Package manager console میتوان به پروژه خود اضافه کرد:
Install-Package MvcScaffolding
Attempting to resolve dependency 'T4Scaffolding'. Attempting to resolve dependency 'T4Scaffolding.Core'. Attempting to resolve dependency 'EntityFramework'. Successfully installed 'T4Scaffolding.Core 1.0.0'. Successfully installed 'T4Scaffolding 1.0.8'. Successfully installed 'MvcScaffolding 1.0.9'. Successfully added 'T4Scaffolding.Core 1.0.0' to MvcApplication1. Successfully added 'T4Scaffolding 1.0.8' to MvcApplication1. Successfully added 'MvcScaffolding 1.0.9' to MvcApplication1.
پس از اینکه بسته MvcScaffolding به پروژه جاری اضافه شد، همان مراحل قبل را که توسط صفحه دیالوگ افزودن یک کنترلر انجام دادیم، اینبار به کمک دستور ذیل نیز میتوان پیاده سازی کرد:
Scaffold Controller Task
نکته و مزیت مهم دیگری که در اینجا در دسترس میباشد، سوئیچهای خط فرمانی است که به همراه صفحه دیالوگ افزودن یک کنترلر وجود ندارند. برای مثال دستور Scaffold Controller را تایپ کرده و سپس یک خط تیره را اضافه کنید. اکنون دکمه tab را مجددا بفشارید. منویی ظاهر خواهد شد که بیانگر سوئیچهای قابل استفاده است.
برای مثال اگر بخواهیم دستور Scaffold Controller Task را با جزئیات اولیه کاملتری ذکر کنیم، مانند تعیین نام دقیق کلاس مدل و کنترلر تولیدی به همراه نام دیگری برای DbContext مرتبط، خواهیم داشت:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext
بهبود مقدماتی کیفیت کد تولیدی MVC Scaffolding
در همان کنسول پاروشل NuGet، کلید up arrow را فشار دهید تا مجددا دستور قبلی اجرا شده ظاهر شود. اینبار دستور قبلی را با سوئیچ جدید Repository (استفاده از الگوی مخزن) اجرا کنید:
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository
Scaffold Controller -ModelType Task -ControllerName TasksController -DbContextType TasksDbContext -Repository -Force
public class TasksController : Controller { private readonly ITaskRepository taskRepository; // If you are using Dependency Injection, you can delete the following constructor public TasksController() : this(new TaskRepository()) { } public TasksController(ITaskRepository taskRepository) { this.taskRepository = taskRepository; }
public interface ITaskRepository : IDisposable { IQueryable<Task> All { get; } IQueryable<Task> AllIncluding(params Expression<Func<Task, object>>[] includeProperties); Task Find(int id); void InsertOrUpdate(Task task); void Delete(int id); void Save(); }
پیاده سازی این اینترفیس در حالت متد Save آن شامل فراخوانی context.SaveChanges است. این مورد باید به الگوی واحد کار (که در اینجا تعریف نشده) منتقل شود. زیرا در یک دنیای واقعی حاصل کار بر روی چندین موجودیت باید در یک تراکنش ذخیره شوند و قرارگیری متد Save داخل کلاس مخزن یا سرویس برنامه، مخزنهای تعریف شده را تک موجودیتی میکند.
اما در کل با توجه به اینکه پیاده سازی منطق کار با موجودیتها به کلاسهای مخزن واگذار شدهاند و کنترلرها به این نحو خلوتتر گردیدهاند، یک مرحله پیشرفت محسوب میشود.