نظرات مطالب
بهبود صفحه‌‌ی بارگذاری اولیه در Blazor WASM
بله. به همین صورت است؛ همان اولین تصویر این مطلب. این رفتار عادی و همیشه به همین صورت است؛ چه با این progress-bar و چه بدون آن (همان حالت پیش فرض بدون autostart=false). فقط الان عملیات صورت گرفته با دقت بیشتری نمایش داده میشود و کاملا مشخص است که اگر فایلها نیازی به دریافت مجدد ندارند (چون کش شدند)، فقط بارگذاری یک فایل js اصلی باقیمانده صورت گرفته.
مطالب
Blazor 5x - قسمت دوم - بررسی ساختار اولیه‌ی پروژه‌های Blazor
پس از آشنایی با دو مدل هاستینگ برنامه‌های 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 این صفحه دقت کنیم، تگ جدید کامپوننت قابل مشاهده‌است:
<body>
   <component type="typeof(App)" render-mode="ServerPrerendered" />
کار آن، رندر کامپوننت App.razor واقع در ریشه‌ی پروژه‌است.
همچنین در همینجا، تگ‌های دیگری نیز قابل مشاهده هستند:
<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>
همانطور که در قسمت قبل نیز عنوان شد، اتصال برنامه‌ی در حال اجرای در مرورگر 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 اشاره می‌کند. همچنین کامپوننت مسیریابی نیز در اینجا ذکر شده‌است:
<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>
اگر شخصی مسیر از پیش تعریف شده‌ای را وارد کند، به قسمت 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 آن، همانند قبل دو متد تنظیم سرویس‌ها و تنظیم میان‌افزارها قابل مشاهده‌است.
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
}
در ConfigureServices آن، سرویس‌های صفحات razor و ServerSideBlazor اضافه شده‌اند. همچنین سرویس نمونه‌ی WeatherForecastService نیز در اینجا ثبت شده‌است.
قسمت‌های جدید متد Configure آن، ثبت مسیریابی توکار BlazorHub است که مرتبط است با اتصال دائم SignalR برنامه و اگر مسیری پیدا نشد، به Host_ هدایت می‌شود:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...

    app.UseEndpoints(endpoints =>
    {
       endpoints.MapBlazorHub();
       endpoints.MapFallbackToPage("/_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 انجام خواهند شد:
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();
        }
    }
}
در اینجا ابتدا کامپوننت 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 آن به صورت زیر است:
<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>
- چون این برنامه، یک برنامه‌ی سمت کلاینت است، اینبار بجای فایل 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 سمت سرور برنامه نیز باید جهت کار و نمایش اطلاعات در سمت کلاینت در دسترس باشد؛ وگرنه قسمت‌های متناظر با آن، قادر به نمایش و یا ثبت اطلاعات نخواهند بود.
نظرات مطالب
کدام w3wp.exe مرتبط با Application جاری من است ؟
به خاطر بعضی دست کاری هایی که روی IIS انجام داده بودم (+ ) VS در مود Debug قرار نمی‌گرفت و پس از فشردن F5 پیغام خطا نمایش می‌داد (با این مضمون که امکان Attach کردن به پروسس وجود ندارد). 
نظرات مطالب
استفاده از popBox برای کوچک کردن خودکار تصاویر بزرگ
سلام
استفاده از update panel برای اینکار مقرون به صرفه نیست. شما از یک طرف از سرعت پائین خط شکایت می‌کنید و از طرف دیگر می‌خواهید از حجم بالای اسکریپت‌های اجکس هم استفاده کنید که فقط به کاربر نشان دهید این تصویر در حال load شدن است؛ کمی صبر کنید الان نمایش داده می‌شود. مثلا چیزی شبیه به این:
http://www.codeproject.com/KB/ajax/DelayedContentLoading.aspx

- راه حل‌های زیادی برای حل این سربار هست. برای مثال در یک CMS که خودم نوشتم، تصاویری را که یوزر آپلود می‌کند، به صورت خودکار تبدیل به thumbnail می‌کنم (System.Drawing.Image.GetThumbnailImage). یعنی دو نسخه نگهداری می‌شود. یکی کوچک و یکی در اندازه اصلی. نسخه کوچک در صفحه اصلی گالری تصاویر نمایش داده می‌شود. پس از کلیک کاربر، با همین پاپ باکس یا یک نمونه دیگر که استفاده کردم به نام light box ، تصویر در اندازه اصلی نمایش داده می‌شود. به این صورت دیگر من نگران نحوه نمایش اولیه نخواهم بود و نمایش اولیه اسکریپتی نیست.
سایت لایت باکس است:
http://www.huddletogether.com/projects/lightbox2/
استفاده از آن هم در ASP.Net ساده است. اسکریپت‌های آن به هدر اضافه می‌شوند. سپس به تگ‌های یک کنترل ایمیج یا هایپرلینک که مثلا در یک DataList قرارگرفته‌اند خواص آن اضافه می‌شود. به این صورت می‌شود یک گالری پویا را ایجاد کرد که با دیتابیس هم کار کند.

- راه دیگر (اگر نمی‌خواهید thumbnail درست کنید) این است که از ابتدا اندازه تصویری را که قرار است نمایش داده شود مشخص کنید (طول و عرض کوچک). البته اگر تصویر شما همانطور که در این مقاله ذکر شد از نوع قرار گرفته شده داخل متن نیست و کاملا کنترل شده مثلا توسط یک دیتالیست رندر می‌شود، به این صورت اندازه ابتدایی کوچک خواهد بود و پس از لود شدن پاپ باکس، با کلیک کاربر روی عکس، اندازه اصلی را خواهید دید. (اسکریپتی که برای پاپ باکس نوشتم اینکار را به صورت خودکار انجام می‌دهد ولی خوب اسکریپت باید لود شده باشد که به طور قطع قبل از لود شدن تصویر اینکار تمام شده)

- اگر تصویر شما مخلوط داخل متن است و کنترلی روی آن ندارید باید با regular expressions آنها را پیدا کنید و سپس خواص مورد نظر خودتان را به تگ‌های تصویر اضافه کنید. (روش سمت سرور قبل از نمایش تصاویر)
مطالب
مشکل در جابجایی پروژه های svn
بنا به دلایلی، کدهای نگهداری شده توسط svn در سیستم خودم رو باید به سیستم دیگری وارد می‌کردم که شامل چند پروژه می‌شد و در قسمت مربوطه چند پروژه کار شده بود. تا این که مشکلی پیش آمد و جهت ادامه توسعه مجبور به برگرداندن پروژه‌ها به سیستم قبلی شدیم. حالا مشکل این بود که به repository قبلی هیچ گونه دسترسی نداشتیم و نمیخواستیم که کدهای در دست هم بصورت یک پروژه تازه وارد سرور svn شود. برای همین مجبور بودیم که از طریق relocate کردن، این کار را انجام دهیم، که به خطای زیر برخورد کردم:

بله؛ مشکل از اینجا ناشی می‌شد که سرور دوم کدها را import نکرده بود و بلکه با یک کامیت ساده، کدها وارد سرور شده بودند.
راه حل: در کل برای تغییر این uid که در تصویر مشخص شده، باید فایل SQLite مربوطه را یافت و به راحتی، دو مشخصه مربوط به آدرس مخزن و uid را در آن اصلاح کرد.
در پوشه svn کنار پروژه، فایلی با نام wc.db از نوع SQLite وجود دارد:

که به سادگی می‌توان آن‌را با برنامه‌ای مانند افزونه ای در فایرفاکس گشود و ویرایش کرد:


  همانطور که مشخص است، در جدول repository، تک ردیفی وجود دارد که مشخصات مربوط به مخزن svn در آن ذخیره شده است:


نکته: در این نوع آدرس دهی، باید حتما به نوع قرار گیری پروژه‌ها در کنار هم نیز دقت داشت. مثلا به محل آخری که در آدرس وجود دارد باید دقت کرد تا در مسیردهی، اشتباهی صورت نگیرد و فایلها جابجا کامیت نشوند.
مطالب
بررسی تغییرات Blazor 8x - قسمت هشتم - مدیریت انتقال اطلاعات Pre-Rendering سمت سرور، به جزایر تعاملی
این Prerendering است که امکان رندر یک کامپوننت تعاملی را در سمت سرور میسر می‌کند تا کاربر بتواند پیش از فعال شدن قابلیت‌های پیشرفته‌ی یک کامپوننت، یک حداقل خروجی را از آن مشاهده کند و همچنین وجود آن برای موتورهای جستجو و بهبود SEO بسیار مفید است. اما ... در این بین مشکلی رخ می‌دهد که نمونه‌ی آن‌را در قسمت قبل مشاهده کردیم: آغاز آن دوبار صورت می‌گیرد؛ یکبار در سمت سرور برای تهیه‌ی یک خروجی SSR و یکبار هم پس از فعال شدن قابلیت‌های تعاملی آن در سمت کلاینت. این آغاز دوباره، برای هر دو حالت کامپوننت‌های تعاملی Blazor Server و Blazor WASM برقرار است. راه‌حل‌هایی از نحوه‌ی مواجه شدن با یک چنین مشکلی را در قسمت قبل بررسی کردیم. راه‌حل دیگری که در این بین ارائه شده و توسط خود مایکروسافت هم در مثال‌های آن مورد استفاده قرار می‌گیرد، استفاده از سرویس PersistentComponentState است که جزئیات آن‌را در این قسمت بررسی خواهیم کرد.


بررسی نحوه‌ی عملکرد سرویس PersistentComponentState

سرویس PersistentComponentState، در دات‌نت 6، به Blazor اضافه شد و امکان جدیدی نیست. قسمتی از این مباحث جدید SSR که به‌نظر مختص به Blazor 8x هستند، پیشتر هم وجود داشتند؛ تحت عنوان pre-rendering. برای مثال فقط کافی بودن تا در برنامه‌های Blazor Server قبلی، فایل Host.cshtml_ را به صورت زیر ویرایش کرد تا pre-rendering فعال شود:
<component type="typeof(App)" render-mode="ServerPrerendered" />
مشکلی که در این حالت بروز می‌کرد این بود که متد OnInitializedAsync یک کامپوننت، دوبار فراخوانی می‌شد؛ یکبار در زمان pre-rendering در سمت سرور، تا HTML استاتیکی برای ارائه‌ی به مرورگر کاربر تولید شود و بار دیگر در زمان فعال شدن اتصال SignalR کامپوننت و ارائه‌ی نهایی تعاملی آن. به همین جهت، کار سنگین آغازین یک کامپوننت، دوبار انجام می‌شد که تجربه‌ی کاربری ناخوشایندی را هم به همراه داشت. برای رفع این مشکل، tag helper ویژه‌ای را به نام persist-component-state تهیه کردند که به صورت زیر به فایل host.cshtml_ اضافه می‌شد:
<body>
    <component type="typeof(App)" render-mode="WebAssemblyPrerendered" /> 
    <persist-component-state />
</body>
این tag-helper فوق است که کار درج رمزنگاری شده‌ی اطلاعات کش شده‌ی pre-rendering سمت سرور را در انتهای فایل HTML صفحه انجام می‌دهد و برای نمونه، نحوه‌ی استفاده‌ی از آن به صورت زیر است:
@page "/"

@implements IDisposable
@inject PersistentComponentState applicationState
@inject IUserRepository userRepository

@foreach(var user in users)
{
    <ShowUserInformation user="@item"></ShowUserInformation>
}

@code {
    private const string cachingKey = "something_unique";
    private List<User> users = new();
    private PersistingComponentStateSubscription persistingSubscription;
    
    protected override async Task OnInitializedAsync()
    {
        persistingSubscription = applicationState.RegisterOnPersisting(PersistData);

        if (applicationState.TryTakeFromJson<List<User>>(cachingKey, out var restored))
        {
            users = restored;
        }
        else 
        {
            users = await userRepository.GetAllUsers();
        }
    }

    public void Dispose()
    {
        persistingSubscription.Dispose();
    }

    private Task PersistData()
    {
        applicationState.PersistAsJson(cachingKey, users);
        return Task.CompletedTask;
    }

}
توضیحات:
- ابتدا تزریق سرویس PersistentComponentState را مشاهده می‌کنید. این همان کامپوننتی است که کار کش کردن اطلاعات را مدیریت می‌کند.
- سپس فراخوانی متد RegisterOnPersisting انجام شده‌است. متدی که توسط آن ثبت می‌شود، در حین عملیات pre-rendering فراخوانی می‌شود تا اطلاعاتی را کش کند. نحوه‌ی این کش شدن را در ادامه‌ی مطلب بررسی می‌کنیم.
- سپس فراخوانی متد TryTakeFromJson رخ‌داده‌است. اگر این متد اطلاعاتی را برگرداند، یعنی pre-rendering سمت سرور پیشتر انجام شده و اطلاعاتی کش شده، برای دریافت در سمت کلاینت، وجود داشته و نیازی به مراجعه‌ی مجدد و دوباره، به بانک اطلاعاتی نیست.
- دراینجا یک قسمت Dispose را هم مشاهده می‌کنید تا اگر کاربر به صفحه‌ی دیگری مراجعه کرد، متد ثبت شده‌ی در اینجا، از لیست مواردی که باید در حین pre-rendering سریالایز شوند، حذف گردد.

اگر از این روش استفاده نشود، به علت دوبار فراخوانی شدن متد OnInitializedAsync یک کامپوننت به همراه pre-rendering، اطلاعاتی که در حین pre-rendering کامپوننت از بانک اطلاعاتی، برای تولید محتوای استاتیک صفحه در سمت سرور دریافت شده، با فعالسازی آتی تعاملی سمت کلاینت آن کامپوننت، از دست خواهد رفت و در این حالت کلاینت باید مجددا این اطلاعات را از بانک اطلاعاتی درخواست کند. بنابراین هدف از persist-component-state، حفظ داده‌ها در بین دو رندر است؛ رندر اولی که در سمت سرور انجام می‌شود و رندر دومی که متعاقبا در سمت کلاینت رخ می‌دهد.

یک نکته: به یک چنین قابلیتی در فریم‌ورک‌های دیگر «hydration» هم گفته می‌شود که در اصل یک شیء دیکشنری است که مقدار شیء حالت را در سمت سرور دریافت کرده و آن‌را در زمان فعال شدن سمت کلاینت کامپوننت، برای استفاده‌ی مجدد مهیا می‌کند.


سؤال: اطلاعات سرویس PersistentComponentState کجا ذخیره می‌شوند؟

همانطور که در مثال فوق هم مشاهده کردید، سرویس PersistentComponentState، این state را به صورت JSON ذخیره می‌کند. اما ... این اطلاعات دقیقا کجا ذخیره می‌شوند؟
برای مشاهده‌ی آن‌ها، فقط کافی است به source صفحه مراجعه کنید تا با دو مقدار مخفی رمزنگاری شده‌ی زیر در انتهای HTML صفحه مواجه شوید!
<!--Blazor-Server-Component-State:CfDJXyz->
<!--Blazor-WebAssembly-Component-State:eyJVc2Xyz->
فرمت این اطلاعات، JsonSerializer.SerializeToUtf8Bytes رمزنگاری شده‌ی توسط IDataProtectionProvider است. این هم یک روش نگهداری اطلاعات، بجای استفاده از کش مرورگر است؛ بدون نیاز به استفاده از جاوااسکریپت و کار با local storage و امثال آن.

مایکروسافت از این نوع کارها پیشتر در ASP.NET Web forms توسط ViewStateها انجام داده‌است! البته ViewStateها جهت نگهداری اطلاعات وضعیت کلاینت، به سمت سرور ارسال می‌شوند؛ اما این Component-Stateهای مخفی، فقط یکبار توسط قسمت کلاینت خوانده می‌شوند و هدف آن‌ها ارسال اطلاعاتی به سمت سرور نیست.

یک نکته: همانطور که عنوان شد، در نگارش‌های قبلی Blazor، از تگ‌هلپری به نام persist-component-state برای درج این اطلاعات در انتهای صفحه استفاده می‌کردند. استفاده از این تگ‌هلپر در Blazor 8x منسوخ شده و به صورت خودکار داده‌های تمام سرویس‌های PersistentComponentState فعالی که توسط PersistAsJson اطلاعاتی را ذخیره می‌کنند، جمع آوری و به صورت یکجا در انتهای صفحه به صورت رمزنگاری شده، درج می‌کنند.


روش خاموش کردن Pre-rendering

برای خاموش کردن pre-rendering نیاز است پارامتر هم‌نامی را به نحو زیر با false مقدار دهی کرد:
@rendermode renderMode

@code 
{
    private static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false);
}


بازنویسی مثال قسمت قبل با استفاده از سرویس PersistentComponentState

در قسمت قبل هرچند روش مواجه شدن با مشکل دوبار فراخوانی شدن متد آغازین یک کامپوننت را در سمت سرور و کلاینت بررسی کردیم، اما ... در نهایت دوبار مراجعه به بانک اطلاعاتی انجام می‌شود؛ یکبار در زمان pre-rendering و با مراجعه‌ی مستقیم به یک سرویس سمت سرور و یکبار دیگر هم در زمان فراخوانی httpClient.GetFromJsonAsync در سمت کلاینت برای دریافت اطلاعات مورد نیاز از یک Web API Endpoint. اگر بخواهیم اطلاعات لیست محصولات دریافتی سمت سرور را به سمت کلاینت منتقل کنیم و مجددا آن‌را از بانک اطلاعاتی دریافت نکنیم، راه‌حل سوم آن، استفاده از سرویس PersistentComponentState است.

در کدهای زیر، بازنویسی کامپوننت محصولات مشابه را مشاهده می‌کنید که کمی پیچیده‌تر شده‌است. اینبار لیست محصولات مشابه، در همان بار اول رندر کامپوننت نمایش داده می‌شوند و نه پس از کلیک بر روی دکمه‌ای. به همین جهت باید کار مدیریت دوبار فراخوانی متد رویدادگردان OnInitializedAsync را به درستی انجام داد. متد OnInitializedAsync یکبار در حین پیش‌رندر سمت سرور اجرا می‌شود و بار دیگر زمانیکه وب‌اسمبلی جاری در مرورگر به صورت کامل دریافت شده و فعال می‌شود؛ یعنی برای بار دوم، کدهای اجرایی آن سمت کلاینت هستند.
در این مثال با استفاده از سرویس PersistentComponentState، اطلاعات دریافت شده‌ی در حین pre-rendering ابتدایی رخ‌داده‌ی در سمت سرور، در متد OnPersistingAsync، کش شده و در حین فعال شدن وب‌اسمبلی مرتبط در سمت کلاینت، با استفاده از متد PersistentState.TryTakeFromJson، از کش خوانده می‌شوند و دیگر دوبار رفت و برگشت به بانک اطلاعاتی را شاهد نخواهیم بود.

@implements IDisposable

@inject IProductStore ProductStore
@inject PersistentComponentState PersistentState

<h3>Related products</h3>

@if (_relatedProducts == null)
{
    <p>Loading...</p>
}
else
{
    <div class="mt-3">
        @foreach (var item in _relatedProducts)
        {
            <a href="/ProductDetails/@item.Id">
                <div class="col-sm">
                    <h5 class="mt-0">@item.Title (@item.Price)</h5>
                </div>
            </a>
        }
    </div>
}

@code{

    private const string StateCachingKey = "products";
    private IList<Product>? _relatedProducts;
    private PersistingComponentStateSubscription _persistingSubscription;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnInitializedAsync()
    {
        _persistingSubscription = PersistentState.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly);

        if (PersistentState.TryTakeFromJson<IList<Product>>(StateCachingKey, out var restored))
        {
            _relatedProducts = restored;
        }
        else
        {
            await Task.Delay(500); // Simulates asynchronous loading
            _relatedProducts = await ProductStore.GetRelatedProducts(ProductId);
        }
    }

    private Task OnPersistingAsync()
    {
        PersistentState.PersistAsJson(StateCachingKey, _relatedProducts);
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _persistingSubscription.Dispose();
    }

}
در این پیاده سازی هنوز هم از سرویس IProductStore استفاده می‌شود که دو نگارش سمت سرور و سمت کلاینت آن‌را در قسمت قبل تهیه کردیم. برای مثال باتوجه به اینکه کدهای فوق در حین pre-rendering در سمت سرور اجرا می‌شوند، به صورت خودکار از نگارش سمت سرور IProductStore استفاده خواهد شد.

نکته‌ی مهم: فعلا کدهای فوق فقط برای حالت بارگذاری اولیه‌ی کامپوننت درست کار می‌کنند. یعنی اگر نگارش وب‌اسمبلی کامپوننت پس از وقوع پیش‌رندر سمت سرور، در مرورگر دریافت و بارگذاری کامل شود؛ اما برای دفعات بعدی مراجعه‌ی به این صفحه با استفاده از enhanced navigation و راهبری بهبود یافته که در قسمت ششم این سری بررسی کردیم ... دیگر کار نمی‌کنند و در این حالت کش شدنی رخ نمی‌دهد که در نتیجه، شاهد دوبار رفت و برگشت به بانک اطلاعاتی خواهیم بود و اساسا استفاده‌ی از PersistentComponentState را زیر سؤال می‌برد؛ چون در حین راهبری بهبود یافته، از آن استفاده نمی‌شود (قسمت PersistentState.TryTakeFromJson آن، هیچگاه اطلاعاتی را از کش نمی‌خواند). اطلاعات بیشتر 
مطالب
بررسی تغییرات Blazor 8x - قسمت نهم - معرفی حالت رندر تعاملی خودکار
Auto Render Mode، آخرین حالت رندری است که به Blazor 8x اضافه شده‌است. اگر از Blazor Server استفاده کنیم، به یک آغاز سریع در برنامه خواهیم رسید، به همراه مقداری تاخیر جزئی، برای به روز رسانی UI؛ از این جهت که تعاملات صورت گرفته باید از طریق اتصال وب‌سوکت SignalR به سرور ارسال شده و منتظر نتیجه‌ی نهایی، برای اعمال آن به صفحه شد و یا باید به مقیاس پذیری این اتصالات همزمان با تعداد کاربران بالا هم اندیشید. اگر از Blazor WASM استفاده کنیم، آغاز آن، اندکی کند خواهد بود تا فایل‌های فریم‌ورک و برنامه، به درون مرورگر کاربر منتقل شوند. اما پس از آن همه‌چیز بسیار سریع است؛ از این جهت که تعاملات با DOM، توسط مرورگر و در همان سمت کاربر مدیریت می‌شود.
اما ... چقدر خوب می‌شد که امکان ترکیب هردوی این‌ها با هم در یک برنامه وجود می‌داشت؛ یعنی داشتن یک آغاز سریع، به همراه تعاملات سریع با DOM. به همین جهت Auto Render Mode به Blazor 8x اضافه شده‌است.


نحوه‌ی عملکرد حالت رندر تعاملی خودکار در Blazor 8x

زمانیکه از قرار است از Auto Render Mode استفاده شود، یعنی در نهایت به سراغ حالت رندر وب‌اسمبلی رفتن؛ اما به شرطی‌که که فریم‌ورک، مطمئن شود می‌تواند تمام فایل‌های مرتبط را خیلی سریع و در کمتر از 100 میلی‌ثانیه تامین کند که عموما یک چنین حالتی به معنای از پیش دریافت کردن این فایل‌ها و کش شده بودن آن‌ها در مرورگر است. اما اگر یک چنین تضمینی وجود نداشته باشد، از همان ابتدای کار تصمیم می‌گیرد که باید کامپوننت را از طریق نگارش Blazor Server آن ارائه دهد، تا آغاز سریعی را سبب شود. در این بین هم در پشت صحنه (یعنی زمانیکه کاربر مشغول به کار با نگارش Blazor Server کامپوننت است)، شروع به دریافت فایل‌های مرتبط با نگارش وب‌اسمبلی کامپوننت و برنامه می‌شود تا آن‌ها را کش کرده و برای بار بعدی بارگذاری صفحه و نمایش اطلاعات آن، به سرعت از آن‌ها استفاده کند.
یک چنین حالتی برای کاربران به این معنا است که به محض گشودن برنامه و صفحه‌ای، قادر به استفاده‌ی از آن هستند و برای بارهای بعدی استفاده، دیگر نیازی به اتصال دائم SignalR یک جزیره‌ی تعاملی Blazor Server نداشته و در نتیجه بار کمتری به سرور تحمیل خواهد شد (مقیاس پذیری بیشتر) و همچنین پردازش DOM بسیار سریعتری را نیز شاهد خواهند بود (کار با نگارش Blazor WASM درون مرورگر).


همانطور که در این تصویر هم مشخص است، برای بار اول نمایش یک چنین جزیره‌هایی، یک اتصال وب‌سوکت برقرار می‌شود که به معنای فعال شدن حالت جزیره‌ای Blazor Server است که در قسمت پنجم بررسی کردیم. در این بین فایل‌های Blazor WASM این جزیره هم دریافت و کش می‌شوند که در کنسول توسعه دهنده‌های مرورگر، لاگ شده‌است. این اتصال وب‌سوکت، در بار اول نمایش این کامپوننت، بسته نخواهد شد؛ تا زمانیکه کاربر به صفحه‌ای دیگر مراجعه کند. در دفعه‌ی بعدی که درخواست نمایش این صفحه را داشته باشیم، چون اطلاعات نگارش وب‌اسمبلی آن کش شده‌است، از همان ابتدای کار نگارش وب اسمبلی را بارگذاری و راه‌اندازی می‌کند.


تفاوت قالب پروژه‌های Auto Render Mode با سایر حالت‌های رندر در Blazor 8x

برای ایجاد قالب ابتدایی پروژه‌ی یک چنین حالت رندری، از دستور dotnet new blazor --interactivity Auto استفاده می‌شود که حالت تعاملی آن به Auto تنظیم شده‌است. در نگاه اول، Solution ایجاد شده‌ی آن، بسیار شبیه به Solution جزیره‌های تعاملی Blazor WASM است که در قسمت هفتم به همراه یک مثال کامل بررسی کردیم؛ یعنی از دو پروژه‌ی سمت سرور و سمت کلاینت تشکیل می‌شود و دارای این تفاوت‌ها است:
در فایل Program.cs پروژه‌ی سمت سرور آن، افزوده شدن هر دو حالت جزایر تعاملی Blazor Server و همچنین Blazor WASM را مشاهده می‌کنیم:
// ...

builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddInteractiveWebAssemblyComponents();

// ...

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode()
    .AddInteractiveWebAssemblyRenderMode()
    .AddAdditionalAssemblies(typeof(Counter).Assembly);
یک چنین قالبی می‌تواند تمام موارد زیر را با هم در یک Solution پشتیبانی کند:
الف) امکان تعریف صفحات فقط SSR در پروژه‌ی سمت سرور
ب) امکان داشتن جزیره‌های تعاملی فقط Blazor Server در پروژه‌ی سمت سرور
ج) امکان داشتن جزیره‌های تعاملی فقط Blazor Wasm در پروژه‌ی سمت کلاینت
د) به همراه امکان تعریف جزیرهای تعاملی Auto Render Mode در پروژه‌ی سمت کلاینت


یک نکته: در این تنظیمات، متد AddAdditionalAssemblies، امکان استفاده از کامپوننت‌های قرار گرفته‌ی در سایر اسمبلی‌ها و پروژه‌ها را میسر می‌کند.


نحوه‌ی تعریف کامپوننت‌هایی که قرار است توسط Auto Render Mode ارائه شوند

باتوجه به اینکه این نوع کامپوننت‌ها در نهایت قرار است به صورت وب‌اسمبلی رندر شوند، آن‌ها را باید در پروژه‌ی سمت کلاینت قرار داد و به نکات مرتبط با توسعه‌ی آن‌ها که در قسمت هفتم پرداختیم، توجه داشت.
همچنین مانند سایر حالت‌های رندر، به دو طریق می‌توان مشخص کرد که یک کامپوننت باید به چه صورتی رندر شود:
الف) استفاده از دایرکتیو حالت رندر با مقدار InteractiveAuto در ابتدای تعریف یک کامپوننت
@rendermode InteractiveAuto
ب) مشخص کردن حالت رندر، در زمان استفاده از المان کامپوننت
<Banner @rendermode="@InteractiveAuto" Text="Hello"/>
البته به شرطی‌که using static زیر را به فایل Imports.razor_ پروژه اضافه کرد:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
مطالب
بهینه سازی پروژه‌های Angular
Angular یکی از محبوب‌ترین فریم ورک‌ها، برای ساختن برنامه‌های تک صفحه‌ای می‌باشد. اگرچه گفتیم تک صفحه‌ای، اما ضرورتا منظور این نیست که پروژه‌ی شما تنها شامل یک صفحه باشد. شما می‌توانید با Angular یک وب سایت را با هزاران صفحه نیز ایجاد کنید. با این حال وقتی بحث از کارآیی باشد، بهتر است همیشه در رابطه با تعدادی فکر کنیم که باعث می‌شوند پروژه، نرم و سریع اجرا شود. 


#1 Optimize main bundle with Lazy Loading
وقتی که پروژه را برای حالت ارائه‌ی نهایی (Production ) بدون در نظر گرفتن Lazy Load، بیلد می‌کنیم احتمالا فایل‌های تولید شده زیر را در پوشه‌ی dist خواهیم دید: 

  1. polyfills.js :  برای ساختن برنامه‌های سازگار با انواع مرورگر‌ها می‌باشد. به دلیل اینکه وقتی کد‌ها را با جدیدترین ویژگی‌ها می‌نویسیم، ممکن است که همه‌ی مرورگر‌ها توانایی پشتیبانی از آن ویژگی‌ها را نداشته باشند.
  2. scripts.js : شامل اسکریپت‌هایی می‌باشد که در بخش scripts، در فایل angular.json تعریف کرده‌ایم.
  3.  webpack loader : runtime.js می‌باشد. این فایل شامل webpack utilities‌هایی می‌باشد که برای بارگذاری دیگر فایل‌ها مورد نیاز است.
  4. styles.css : شامل style ‌هایی است که در بخش styles، در فایل angular.json تعریف کرده‌ایم.
  5. main.js : شامل تمامی کد‌ها از قبیل کامپوننت‌ها ( کد‌های مربوط به css ، html ، ts) ، دایرکتیو‌ها، pipes و سرویس‌ها و ماژول‌های ایمپورت شده از جمله third party‌های می‌باشد.

همانطور که متوجه هستید، فایل main.js در طول زمان بزرگتر و بزرگتر خواهد شد که این یک مشکل است. در این حالت برای مشاهده‌ی وب سایت، مرورگر نیاز دارد که فایل main.js را دانلود کرده و سپس در صفحه،  آن را اجرا و رندر کند که این یک چالش برای کاربران موبایل با اینترنت ضعیف و هم چنین کاربران دسکتاپ می‌باشد. 
آسان‌ترین راه برای برطرف کردن این مشکل این است که پروژه را به چندین ماژول lazy load، تقسیم کنیم. وقتی که از lazy module‌ها استفاده می‌کنیم، انگیولار chunk مربوط به آن را تولید می‌کند که در ابتدا بارگذاری نخواهد شد؛ مگر اینکه مورد نیاز باشند (معمولا با فعال سازی یک مسیر اتفاق می‌افتد). 
وقتی که از lazy loading استفاده می‌کنیم، بعد از فرایند build، فایل‌های جدیدی تولید خواهند شد، مثل  4.386205799sfghe4.js که یک  چانک از  یک lazy module می‌باشد و در زمان راه اندازی صفحه اول اجرا نخواهد شد که نتیجه‌ی آن داشتن فایل main.js ای با حجم کم می‌باشد. بنابراین بارگذاری صفحه‌ی اول، خیلی سریع انجام خواهد شد. 

با این حال، بارگذاری هر قسمت می‌تواند بر روی کارآیی تاثیر داشته باشد (بارگذاری ممکن است کند باشد). خوشبختانه انگیولار یک راه را برای برطرف کردن این مشکل فراهم کرده است ( PreloadingStrategy ) . بعد از اینکه فایل main.js  به صورت کامل بارگذاری و اجرا شد، کار پیش واکشی ماژول‌ها را انجام می‌دهد و زمانیکه کاربری مسیری را درخواست می‌دهد، آْن مسیر را بدون درنگ مشاهده خواهد کرد.

#2 Debug bundles with Webpack Bundle Analyzer 
حتی ممکن است بعد از تقسیم کردن منطق برنامه به چند ماژول lazy load، شما یک فایل main.js بزرگ داشته باشید. در این حالت می‌توانید بهینه سازی بیشتری را با استفاده از Webpack Bundle Analyzer انجام دهید. با استفاده از این پکیج می‌توانید آمار‌هایی را در رابطه با هر باندل داشته باشید. در ابتدا با استفاده از دستور زیر پکیج آن‌را نصب کنید:
npm install --save-dev webpack-bundle-analyzer
سپس فایل package.json را باز کرده و در بخش scripts، مدخل زیر را اضافه کنید:
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"
توجه کنید که اگر خروجی برنامه شما مستقیما در dist می‌باشد، به صورت بالا عمل کنید؛ ولی اگر خروجی برنامه شما در پوشه‌ی dist/YourApplicationName باشد، آن را به حالت زیر ویرایش کنید:
"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/YourApplicationName/stats.json"
در نهایت دستور زیر را اجرا کنید:
npm run bundle-report
دستور بالا یک بیلد را برای پروژه برای حالت ارائه‌ی نهایی  (Production) همراه با  آمار‌هایی در رابطه با هر باندل ایجاد می‌کند. در اینجا می‌توانیم ببینیم که چه ماژولها/فایل‌هایی در هر باندل استفاده شده‌است. این مورد فوق العاده کمک می‌کند. هم چنین می‌توانیم به صورت بصری ببینیم که چه چیز‌هایی در هر ماژول شامل شده‌اند که بهتر بود آنجا نباشند:


#3 Use Lazy Loading for images that are not visible in page 
وقتی که صفحه اصلی را در اولین بار، بارگذاری می‌کنیم، می‌توانیم تصاویری را داشته باشیم که در صفحه‌ی نمایش، نمایان نباشند (منظور viewport می‌باشد) و کاربر برای دیدن آن تصاویر باید صفحه را به پایین اسکرول کند. با این وجود وقتی که صفحه بارگذاری می‌شود، تصاویر هم بلافاصله دانلود می‌شوند. اگر تعداد تصاویر زیاد باشند این مورد می‌تواند بر روی performance تاثیر منفی داشته باشد. برای برطرف کردن این مشکل می‌توان از lazy loading تصاویر استفاده کرد. در این حالت تصاویر زمانی بارگذاری می‌شوند که کاربر به آن‌ها می‌رسد. یک JavaScript API به نام Intersection Observer API وجود دارد که باعث می‌شود پیاده سازی lazy load خیلی آسان شود. علاوه بر این می‌توان یک دایرکتیو را با قابلیت استفاده مجدد طراحی کرد ( lazy loading برای تصاویر با استفاده از Intersection Observer).

#4 Use virtual scrolling for large lists  
با استفاده از virtual scrolling  می‌توان المنت‌ها را در Dom بر اساس بخش‌های قابل مشاهده‌ای از یک لیست، Load  یا unload کرد که برنامه را فوق العاده سریع می‌کند.
 
#5 Use FOUT instead of FOIT for fonts  
در بیشتر وب سایت‌ها می‌توان فونت‌های سفارشی زیبایی را به جای فونت‌های معمول دید. با این حال برای استفاده از فونت‌های فراهم شده توسط سرویس‌های دیگر لازم است که مرورگر آن‌ها را دانلود و parse  کند. اگر از فونت‌های سفارشی استفاده کنیم، مثل  Google Fonts، چه اتفاقی می‌افتد؟ در اینجا دو سناریو وجود دارد: 
  • در این حالت مرورگر منتظر می‌ماند تا فونت دانلود شود و آن را parse  کند و تنها بعد از آن متن را بر روی صفحه نمایش می‌دهد. متن روی صفحه تا زمانیکه فونت دانلود و parse  نشده باشد، قابل مشاهده نیست؛ این FOIT است (Flash of invisible text) .
  • مرورگر در ابتدا متن را با استفاده از فونت معمول نمایش می‌دهد و بعد از آن سعی می‌کند که ساختار‌های فونت خارجی را دریافت کند. وقتی که دانلود انجام شد و سپس آن را parse  کرد، فونت سفارشی دانلود شده، با فونت معمول جایگزین خواهد شد؛ این FOUT است ( Flash of unstyled text ).

بیشتر مرورگر‌ها از FOIT  استفاده می‌کنند و تنها Internet Explorer از FOUT استفاده می‌کند. برای برطرف کردن این مشکل می‌توان از توصیف‌گر font-display استفاده کرد و به مرورگر بگوییم که می‌خواهیم در ابتدا متن را با فونت معمول نمایش دهیم و جایگزینی فونت، بعد از دانلود باشد (بیشتر).
مطالب
با ASP.MVC چه مزایایی را به دست خواهیم آورد
عموما در اکثر مطالب مقایسه‌ای بین وب فرم‌ها و ASP.NET MVC به جداسازی بهتر منطق کدها از فرم‌ها و قابلیت بهتر تهیه آزمون‌های واحد اشاره می‌شود. در این مطلب از دیدگاهی دیگر به این مساله خواهیم پرداخت؛ از لحاظ فنی و جدای از مسایل یاد شده، چه مزایای دیگری را می‌توان با استفاده از ASP.NET MVC نسبت به وب فرم‌ها به دست آورد؟

1- آدرس‌های تمیزتر
در ASP.NET MVC به صورت پیش فرض از سیستم Routing موجود در زیر ساخت ASP.NET برای نمایش Urlهایی بدون پسوند استفاده می‌شود. همچنین این سیستم امکان تهیه آدرس‌هایی با سازگاری بهتر با موتورهای جستجو را نیز از ابتدا مدنظر داشته است.
بله. این زیر ساخت در اختیار وب فرم‌ها نیز هست؛ اما فرق است بین حالتی که از ابتدا مجبور شویم تمیزتر کار کنیم با زمانیکه این انتخاب را داریم و ... عموما هم از آن در وب فرم‌ها استفاده نمی‌شود.

2- عدم وابستگی الزامی به فایل‌های فیزیکی موجود در سیستم
کلیه درخواست‌ها در MVC برخلاف وب فرم‌ها در بدو امر به فایل‌های موجود در سیستم منتقل نمی‌شوند. درخواست‌ها به متدهای موجود در کنترلرها منتقل می‌شوند. همین مساله سبب می‌شود که آدرس‌ها الزاما به یک فایل فیزیکی موجود در سیستم اشاره نکنند. به این ترتیب می‌توان درخواست‌ها را بر اساس شرایط، به Viewهای مختلف هدایت کرد و نه اینکه هر درخواست ابتدا به یک view رسیده و سپس به متدی ارجاع داده شود.
این مساله از لحاظ امنیتی نیز مهم است. درخواست‌های رسیده به MVC سبب اجرای هیچ فرمی نخواهند شد. درخواست‌ها حتما باید از فیلتر یک کنترلر عبور کنند تا اجرایی شوند.

3- امکان مدیریت بهتر قسمت‌های مختلف سایت در پوشه‌های جداگانه
اگر به سورس اکثر سایت‌های مبتنی بر ASP.NET Web forms توجه کنیم، تمام فایل‌های آن‌ها در ریشه سایت قرار دارند منهای فایل‌های CSS و JS و تصاویر. در ASP.NET MVC از ابتدای کار، هر قسمت از سایت در پوشه‌های جداگانه‌ای قرار می‌گیرد و به این ترتیب مدیریت فایل‌ها و نظم دهی به آن‌ها ساده‌تر خواهد بود.

4- امکان تعریف تمام اجزای یک فرم یا view به صورت strongly typed
در ASP.NET MVC می‌توان یک کلاس را به یک فرم یا View نسبت داد. به این ترتیب کنترل‌های web forms تبدیل به خواص این کلاس در MVC خواهند شد. مزیت این امر امکان کنترل تمام اجزای فرم‌های سایت توسط کامپایلر است.
به این ترتیب اگر در طی یک حلقه، جدولی را ایجاد کنیم، تمام عناصر تشکیل دهنده این حلقه (چه کدهای آن و چه المان‌هایی که اطلاعات را در صفحه نمایش می‌دهند) نیز توسط کامپایلر قابل بررسی و خطایابی هستند.

5- مقدار دهی خودکار مدل متناظر با یک فرم یا View در ASP.NET MVC
روال متداول کار با وب فرم‌ها، قرار دادن تعدادی کنترل در صفحه و سپس دریافت دستی مقادیر آن‌ها در فایل code behind است. در MVC دیگر نیازی نیست تا این کارها را دستی انجام دهید. به یک فرم یا View کلاسی را انتساب خواهید داد. فریم ورک خواص آن‌را به صورت خودکار در حین ارسال به سرور مقدار دهی خواهد کرد. این مورد حتی در حین کار با jQuery Ajax نیز صادق  است.
این مساله کار با ORMها را نیز ساده‌تر می‌کند. از این جهت که تمام آن‌ها نهایتا با یک سری مدل و کلاس کار خواهند کرد و تمام فیلدها و جداول به تعدادی کلاس و خاصیت تعریف شده در آن‌ها نگاشت می‌شوند.
به این ترتیب چون دیگر نیازی به ارجاع مستقیم به اشیاء بصری در فایل‌های code behind که در اینجا کنترلر نام گرفته‌اند نیست، نوشتن آزمون واحد برای این کلاس‌ها نیز به شدت ساده‌تر شده است.

6- ASP.NET MVC به همراه یک فرم ساز توکار ارائه می‌شود
اگر کسی به شما گفته است که سرعت کار با ASP.NET MVC پایین است به طور قطع دو فصل اول یک کتاب MVC را بیشتر مطالعه نکرده است. در MVC یک کلاس متناظر با فرمی را طراحی می‌کنید. توسط ابزار scaffolding همراه با VS.NET از روی این کلاس و مدل، با چند کلیک یک فرم تولید خواهد شد. فرمی که حتی مقدار دهی و انتساب عناصر بصری آن به کلاس متناظر با آن نیز خودکار است.
سرعت پیاده سازی یک برنامه با ASP.NET MVC به مراتب بیشتر است از کار با وب فرم‌ها.

7- حذف View State در MVC
از آنجائیکه فرم‌های ASP.NET Web forms از نوع strongly typed نیستند (در دات نت 4 و نیم اندکی بهبود در حد گرید‌های آن حاصل شده البته)، برای اینکه پس از ارسال یک فرم به سرور باز هم کنترل‌های نمایش داده شده در صفحه همان مقادیر قبلی را نمایش دهند، مکانیزم View State به همراه ذخیره سازی اطلاعات فرم در فیلدهای مخفی فرم جاری طراحی شد.
در MVC نیازی به این مکانیزم نیست. زیرا فقط کافی است که اطلاعات مدل را مجددا به View ارسال کنیم. نمایش و انتساب نهایی آن در اینجا خودکار است. بنابراین نیازی به View State وجود ندارد.

8- کنترل بهتر بر روی اعتبار سنجی اطلاعات دریافتی
در وب فرم‌ها اگر بخواهیم سیستم اعتبارسنجی آن‌را غیرفعال کنیم، مثلا برای دریافت html از کاربر، نیاز است کلا آن‌را از کار بیندازیم (یا در سطح فرم یا در سطح کل برنامه). در MVC می‌توان این اعتبار سنجی را تنها در سطح یک خاصیت که قرار است اطلاعات HTML ایی را دریافت کند، غیرفعال کرد؛ نه برای کل فرم و نه در سطح کل برنامه.

9- امکان استفاده از فرم‌های و Viewهای Razor بجای موتور وب فرم‌ها
در وب فرم‌ها تا این زمان فقط محدود به تنها موتور نمایشی مخصوص به آن هستیم. اما در MVC این محدودیت برداشته شده و تا به حال چندین و چند View engine در این بین توسط مایکروسافت و سایر برنامه نویس‌ها طراحی شده است. مهم‌ترین آن‌ها Razor است که تمام برنامه نویس‌های MVC پس از مدتی به روان بودن و طراحی طبیعی و عالی آن اعتراف دارند.

10- امکان تعریف بیش از یک فرم در صفحه
طراحی ASP.NET Web forms از روز اول آن محدود به یک فرم در صفحه بوده است. این محدودیت در MVC برداشته شده و مزیت آن امکان ارسال اطلاعات قسمت‌های مختلف یک فرم به کنترلرهای مختلف و جداسازی بهتر کدهای قسمت‌های مختلف برنامه است.

11- امکان Refactoring بهتر کدهای تکراری در ASP.NET MVC به کمک مفهوم فیلترها
فیلترها در MVC یک سری attribute هستند که می‌توان آن‌ها را به متدهای کنترلرها اعمال کرد و به صورت خودکار توسط فریم ورک پیش یا پس از اجرای یک متد اجرا می‌شوند. به این ترتیب حجم قابل ملاحظه‌ای از if و else‌ها را می‌توان در این فیلترها کپسوله کرد و کدهای متدهای کنترلرها را تمیزتر و زیباتر نمود.

12- سازگاری کامل با jQuery و jQuery Ajax و کلا انواع و اقسام فریم‌ورک‌های جاوا اسکریپتی
در MVC وب کنترل‌ها کنار گذاشته شده‌اند و سعی شده است با وب به همان نحو که هست برخورد شود. به این ترتیب اگر نیاز داشتید، کل دکمه‌های فرم را با spanها جایگزین کرده و توسط فریم ورک‌های CSS ایی تزئین کنید، بدون نیاز به نگارش جدیدی از ASP.NET MVC، باز هم برنامه کار خواهد کرد.
یا برای کار با اجزای مختلف فرم از ده‌ها و صدها افزونه موجود برای jQuery به سادگی می‌توان استفاده کرد. برای نمونه کل سیستم اعتبار سنجی توکار MVC با اعتبار سنجی jQuery یکپارچه و جایگزین شده است.
یا برای کار با jQuery Ajax نیازی نیست تا متدی را static تعریف کنید و به این ترتیب از مزایای امنیتی توکار ASP.NET محروم شوید (مثلا دسترسی به شیء User اعتبار سنجی مبتنی بر فرم‌ها). یا اگر فرم شما 10 فیلد دارد، کل این فیلدها به صورت خودکار به خواص متناظر با آن‌ها نگاشت خواهد شد و نیازی نیست برای این مورد کد بنویسید. به علاوه باید درنظر داشت که jQuery Ajax نسبت به فریم ورک Ajax همراه با ASP.NET web forms بسیار سبک‌تر و سریعتر عمل می‌کند چون نیازی ندارد تا هر بار View state را نیز به سرور ارسال کند.
همچنین در اینجا دیگر ID کنترل‌های مورد استفاده در اسکریپت‌ها به صورت خودکار تولید نمی‌شوند و برنامه نویس از ابتدای امر کنترل کاملی را روی این مساله دارد.

13- امکانات فشرده سازی css و js بهتر
در MVC 4 سیستم bundling آن از نمونه مشابه موجود در وب فرم‌ها کامل‌تر است و جهت فشرده سازی و یکی کردن هر دو مورد فایل‌های css و js می‌تواند بکارگرفته شود؛ به همراه تنظیمات کش مرورگر و gzip خودکار حاصل. به علاوه این سیستم را سفارشی سازی نیز می‌توان ساخت و بهینه سازی عملکرد آن مطابق نیاز میسر است.

14- یکپارچگی بهتر EF Code first با MVC
عنوان شد که وجود مدل‌ها و فرم‌های strongly typed یکی از مزایای کار با MVC است و ORMها نیز نهایتا با همین کلاس‌ها هستند که کار می‌کنند. در MVC سیستم کد سازی به نام scaffolding وجود دارد (تهیه شده توسط خود مایکروسافت) که می‌تواند بر اساس مدل‌های EF code first شما، قسمت عمده‌ای از کدهای یک برنامه ASP.NET MVC را تولید کند. سپس می‌توانید به سفارشی سازی آن مشغول شوید.

15- تزریق وابستگی‌ها در MVC ساده‌تر است
در هر دو فریم ورک وب فرم‌ها و MVC امکان تزریق وابستگی‌ها وجود دارد. اما در MVC می‌توان در میانه کار وهله سازی کنترلرها، دخالت کرد و کنترل آن را کاملا در دست گرفت. همین امر سبب می‌شود حین کار با کتابخانه‌های تزریق وابستگی‌ها در ASP.NET MVC حجم کد نویسی به شدت کاهش پیدا کند.

16- امکانات امنیتی MVC بیشتر است
عنوان شد که در MVC می‌توان اعتبار سنجی را تنها در حد یک خاصیت غیرفعال کرد. فیلتر مبارزه با حملات CSRF جزئی از فریم ورک MVC است. به همراه فیلتر Authorize آن که باز هم اعمال سفارشی سیستم اعتبار سنجی مبتنی بر فرم‌ها را ساده‌تر می‌کند با امکان یکپارچگی بهتر با Role providerهای سفارشی.
و یا برای نمونه Razor به صورت پیش فرض امن طراحی شده است. خروجی Razor همواره و در بدو امر، html encoded است مگر اینکه برنامه نویس آگاهانه آن‌را تغییر دهد. این مورد مقاومت در برابر حملات XSS را بالا خواهد برد.
امکان استفاده از فیلترهای سفارشی که عنوان شد، جهت مسایل امنیتی بسیار کاربرد دارند. برای مثال بررسی referrer فرم ارسال به سرور را درنظر بگیرید. در وب فرم‌ها می‌توان این‌کار را با یک http module که روی کل برنامه تاثیر گذار است انجام داد. اما در MVC این فیلتر را تنها می‌توان بر روی یک فرم خاص عمومی برای مثال اعمال کرد و نه کل برنامه.