The ASP.NET team is proud to announce general availability of ASP.NET Core 2.0. This release features compatibility with .NET Core 2.0, tooling support in Visual Studio 2017 version 15.3, and the new Razor Pages user-interface design paradigm. For a full list of updates, you can read the release notes. The latest SDK and tools can be downloaded from https://dot.net/core. Read the .NET Core 2.0 release announcement for more information and watch the launch video on Channel 9.
بررسی ویژگیهای جدید C# 8
C# 8 was officially released in September at .NET Conf along side .NET Core 3 & Visual Studio 16.3 (8.3 on Mac). It is packed with tons of amazing new features that truly everyone should be using.
We are thrilled to announce the highly anticipated .NET Conf 2024, a free, three-day virtual developer event celebrating the release of .NET 9. Co-organized by the .NET community and Microsoft, this annual tradition continues to grow, and we’re more excited than ever to bring you the latest innovations in .NET. Mark your calendars for November 12th to 14th, 2024, and prepare to be inspired by a wealth of knowledge, creativity, and community engagement.
معرفی قالبهای جدید شروع پروژههای Blazor در دات نت 8
پس از نصب SDK دات نت 8، دیگر خبری از قالبهای قدیمی پروژههای blazor server و blazor wasm نیست! در اینجا در ابتدا باید مشخص کرد که سطح تعاملی برنامه در چه حدی است. در ادامه 4 روش شروع پروژههای Blazor 8x را مشاهده میکنید که توسط پرچم interactivity--، نوع رندر برنامه در آنها مشخص شدهاست:
اجرای قسمتهای تعاملی برنامه بر روی سرور:
dotnet new blazor --interactivity Server
اجرای قسمتهای تعاملی برنامه در مرورگر، توسط فناوری وباسمبلی:
dotnet new blazor --interactivity WebAssembly
برای اجرای قسمتهای تعاملی برنامه، ابتدا حالت Server فعالسازی میشود تا فایلهای WebAssembly دریافت شوند، سپس فقط از WebAssembly استفاده میکند:
dotnet new blazor --interactivity Auto
فقط از حالت SSR یا همان static server rendering استفاده میشود (این نوع برنامهها تعاملی نیستند):
dotnet new blazor --interactivity None
سایر گزینهها را با اجرای دستور dotnet new blazor --help میتوانید مشاهده کنید.
نکتهی مهم! در قالبهای آمادهی Blazor 8x، حالت SSR، پیشفرض است.
هرچند در تمام پروژههای فوق، انتخاب حالتهای مختلف رندر را مشاهده میکنید، اما این انتخابها صرفا دو مقصود مهم را دنبال میکنند:
الف) تنظیم فایل Program.cs برنامه جهت افزودن وابستگیهای مورد نیاز، به صورت خودکار.
ب) ایجاد پروژهی کلاینت (علاوه بر پروژهی سرور)، در صورت نیاز. برای مثال حالتهای وباسمبلی و Auto، هر دو به همراه یک پروژهی کلاینت وباسمبلی هم هستند؛ اما حالتهای Server و None، خیر.
در تمام این پروژهها هر صفحه و یا کامپوننتی که ایجاد میشود، به صورت پیشفرض بر اساس SSR رندر و نمایش داده خواهد شد؛ مگر اینکه به صورت صریحی این نحوهی رندر را بازنویسی کنیم. برای مثال مشخص کنیم که قرار است بر اساس Blazor Server اجرا شود و یا وباسمبلی و یا حالت Auto.
بررسی حالت Server side rendering
برای بررسی این حالت یک پوشهی جدید را ایجاد کرده و توسط خط فرمان، دستور dotnet new blazor --interactivity Server را در ریشهی آن اجرا میکنیم. پس از ایجاد ساختار ابتدایی پروژه بر اساس این قالب انتخابی، فایل Program.cs جدید آن، چنین شکلی را دارد:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); app.MapRazorComponents<App>().AddInteractiveServerRenderMode(); app.Run();
server-side rendering به این معنا است که برنامهی سمت سرور، کل DOM و HTML نهایی را تولید کرده و به مرورگر کاربر ارائه میکند. مرورگر هم این DOM را نمایش میدهد. فقط همین! در اینجا هیچ خبری از اتصال دائم SignalR نیست و محتوای ارائه شده، یک محتوای استاتیک است. این حالت رندر، برای ارائهی محتواهای فقط خواندنی غیرتعاملی، فوق العادهاست؛ امکان از لحظهای که نیاز به کلیک بر روی دکمهای باشد، دیگر پاسخگو نیست. به همین جهت در اینجا امکان تعاملی کردن تعدادی از کامپوننتهای ویژه و مدنظر نیز پیشبینی شدهاند تا بتوان به ترکیبی از server-side rendering و client-side rendering رسید.
حالت پیشفرض در اینجا، ارائهی محتوای استاتیک است. بنابراین هر کامپوننتی در اینجا ابتدا بر روی سرور رندر شده (HTML ابتدایی آن آماده شده) و به سمت مرورگر کاربر ارسال میشود. اگر کامپوننتی نیاز به امکانات تعاملی داشت باید آنرا دقیقا توسط ویژگی InteractiveXYZ مشخص کند؛ مانند مثال زیر:
@page "/counter" @rendermode InteractiveServer <PageTitle>Counter</PageTitle> <h1>Counter</h1> <p role="status">Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }
در ادامه، مجددا سطر کامنت شده را به حالت عادی برگردانید و سپس برنامه را اجرا کنید. پیش از باز کردن صفحهی Counter، ابتدا developer tools مرورگر خود را گشوده و برگهی network آنرا انتخاب و سپس صفحهی Counter را باز کنید. در این لحظهاست که مشاهده میکنید یک اتصال وبسوکت برقرار شد. این اتصال است که قابلیتهای تعاملی صفحه را برقرار کرده و مدیریت میکند (این اتصال دائم SignalR است که این صفحه را همانند برنامههای Blazor Web Server پیشین مدیریت میکند).
یک نکته: در برنامههای Blazor Server سنتی، امکان فعالسازی قابلیتی به نام prerender نیز وجود دارد. یعنی سرور، ابتدا صفحه را رندر کرده و محتوای استاتیک آنرا به سمت مرورگر کاربر ارسال میکند و سپس اتصال SignalR برقرار میشود. در دات نت 8، این حالت، حالت پیشفرض است. اگر آنرا نمیخواهید باید به نحو زیر غیرفعالش کنید:
@rendermode InteractiveServerRenderModeWithoutPrerendering @code{ static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = new InteractiveServerRenderMode(false); }
روشی ساده برای تعاملی کردن کل برنامه
اگر میخواهید رفتار برنامه را همانند Blazor Server سابق کنید و نمیخواهید به ازای هر کامپوننت، نحوهی رندر آنرا به صورت سفارشی انتخاب کنید، فقط کافی است فایل App.razor را باز کرده:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="MyApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
<HeadOutlet @rendermode="@InteractiveServer" /> ... <Routes @rendermode="@InteractiveServer" />
در این حالت دیگر نیازی نیست تا به ازای هر کامپوننت و صفحه، نحوهی رندر را مشخص کنیم؛ چون این نحوه، از بالاترین سطح، به تمام زیرکامپوننتها به ارث میرسد (دربارهی این نکته در قسمت قبل، توضیحاتی ارائه شد).
بررسی حالت Streaming Rendering
در اینجا مثال پیشفرض Weather.razor قالب پیشفرض مورد استفادهی جاری را کمی تغییر دادهایم که کدهای نهایی آن به صورت زیر است (2 قسمت forecasts.AddRange_ را اضافهتر دارد):
@page "/weather" @attribute [StreamRendering(prerender: true)] <PageTitle>Weather</PageTitle> <h1>Weather</h1> <p>This component demonstrates showing data.</p> @if (_forecasts == null) { <p> <em>Loading...</em> </p> } else { <table class="table"> <thead> <tr> <th>Date</th> <th>Temp. (C)</th> <th>Temp. (F)</th> <th>Summary</th> </tr> </thead> <tbody> @foreach (var forecast in _forecasts) { <tr> <td>@forecast.Date.ToShortDateString()</td> <td>@forecast.TemperatureC</td> <td>@forecast.TemperatureF</td> <td>@forecast.Summary</td> </tr> } </tbody> </table> } @code { private List<WeatherForecast>? _forecasts; protected override async Task OnInitializedAsync() { // Simulate asynchronous loading to demonstrate streaming rendering await Task.Delay(500); var startDate = DateOnly.FromDateTime(DateTime.Now); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching", }; _forecasts = GetWeatherForecasts(startDate, summaries).ToList(); StateHasChanged(); // Simulate asynchronous loading to demonstrate streaming rendering await Task.Delay(1000); _forecasts.AddRange(GetWeatherForecasts(startDate, summaries)); StateHasChanged(); await Task.Delay(1000); _forecasts.AddRange(GetWeatherForecasts(startDate, summaries)); } private static IEnumerable<WeatherForecast> GetWeatherForecasts(DateOnly startDate, string[] summaries) { return Enumerable.Range(1, 5) .Select(index => new WeatherForecast { Date = startDate.AddDays(index), TemperatureC = Random.Shared.Next(-20, 55), Summary = summaries[Random.Shared.Next(summaries.Length)], }); } private class WeatherForecast { public DateOnly Date { get; set; } public int TemperatureC { get; set; } public string? Summary { get; set; } public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } }
در ادامه مجددا سطر ویژگی StreamRendering را به حالت قبلی برگردانید و برنامه را اجرا کنید. در این حالت ابتدا قسمت loading ظاهر میشود و سپس در طی چند مرحله با توجه به Task.Delayهای قرار داده شده، صفحه رندر شده و تکمیل میشود.
اتفاقی که در اینجا رخ میدهد، استفاده از فناوری HTML Streaming است که مختص به مایکروسافت هم نیست. در حالت Streaming، هربار قطعهای از HTML ای که قرار است به کاربر ارائه شود، به صورت جریانی به سمت مرورگر ارسال میشود و مرورگر این قطعهی جدید را بلافاصله نمایش میدهد. نکتهی جالب این روش، عدم نیاز به اتصال SignalR و یا اجرای WASM درون مرورگر است.
Streaming rendering حالت بینابین رندر کامل در سمت سرور و رندر کامل در سمت کلاینت است. در حالت رندر سمت سرور، کل HTML صفحه ابتدا توسط سرور تهیه و بازگشت داده میشود و کاربر باید تا پایان عملیات تهیهی این HTML نهایی، منتظر باقی بماند و در این بین چیزی را مشاهده نخواهد کرد. در حالت Streaming rendering، هنوز هم همان حالت تهیهی HTML استاتیک سمت سرور برقرار است؛ به همراه تعدادی محل جایگذاری اطلاعات جدید. به محض پایان یک عمل async سمت سرور که سه نمونهی آن را در مثال فوق مشاهده میکنید، برنامه، جریان قطعهای از اطلاعات استاتیک را به سمت مرورگر کاربر ارسال میکند تا در مکانهایی از پیش تعیین شده، درج شوند.
در حالت SSR، فقط یکبار شانس ارسال کل اطلاعات به سمت مرورگر کاربر وجود دارد؛ اما در حالت Streaming rendering، ابتدا میتوان یک قالب HTML ای را بازگشت داد و سپس مابقی محتوای آنرا به محض آماده شدن در طی چند مرحله بازگشت داد. در این حالت نمایش گزارشی از اطلاعاتی که ممکن است با تاخیر در سمت سرور تهیه شوند، سادهتر میشود. یعنی میتوان هربار قسمتی را که تهیه شده، برای نمایش بازگشت داد و کاربر تا مدت زیادی منتظر نمایش کل صفحه باقی نخواهد ماند.
روش نهایی معرفی نحوهی رندر صفحات
بجای استفاده از ویژگیهای RenderModeXyz جهت معرفی نحوهی رندر کامپوننتها و صفحات (که تا پیش از نگارش RTM معرفی شده بودند و چندبار هم تغییر کردند)، میتوان از دایرکتیو جدیدی به نام rendermode@ با سه مقدار InteractiveServer، InteractiveWebAssembly و InteractiveAuto استفاده کرد. برای سهولت تعریف این موارد باید سطر ذیل را به فایل Imports.razor_ اضافه نمود:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@attribute [RenderModeInteractiveServer]
@rendermode InteractiveServer
اگر هم قصد سفارشی سازی آنها را دارید، برای مثال میخواهید prerender را در آنها false کنید، روش کار به صورت زیر است:
@rendermode renderMode @code { static IComponentRenderMode renderMode = new InteractiveWebAssemblyRenderMode(prerender: false); }
معرفی عملگر Hat
برای دسترسی به آخرین عضو یک آرایه عموما از روش زیر استفاده میشود:
var integerArray = new int[3]; var lastItem = integerArray[integerArray.Length - 1];
var integerList = integerArray.ToList(); integerList.Last();
var secondToLast = integerArray[integerArray.Length - 2];
این شمردنهای از آخر در C# 8.0 توسط ارائهی عملگر hat یا همان ^ که پیشتر کار xor را انجام میداد (و البته هنوز هم در جای خودش همین مفهوم را دارد)، میسر شدهاست:
var lastItem = integerArray[^1];
نکتهی مهم: کسانیکه شروع به آموزش برنامه نویسی میکنند، مدتی طول میکشد تا عادت کنند که اولین ایندکس یک آرایه از صفر شروع میشود. در اینجا باید درنظر داشت که با بکارگیری «عملگر کلاه»، آخرین ایندکس یک آرایه از «یک» شروع میشود و نه از صفر. برای نمونه در مثال زیر به خوبی تفاوت بین ایندکس از ابتدا و ایندکس از انتها را میتوانید مشاهده کنید:
var words = new string[] { // index from start index from end "The", // 0 ^9 "quick", // 1 ^8 "brown", // 2 ^7 "fox", // 3 ^6 "jumped", // 4 ^5 "over", // 5 ^4 "the", // 6 ^3 "lazy", // 7 ^2 "dog" // 8 ^1 }; // 9 (or words.Length) ^0
در حالت کلی ایندکس n^ معادل sequence.Length - n است. بنابراین sequence[^0] به معنای sequence[sequence.Length] است و هر دو مورد یک index out of range exception را صادر میکنند.
IDE نیز با فعال سازی C# 8.0، زمانیکه به قطعه کد زیر میرسد، زیر words.Length - 1 خط کشیده و پیشنهاد میدهد که بهتر است از 1^ استفاده کنید:
Console.WriteLine($"The last word is {words[words.Length - 1]}");
معرفی نوع جدید Index
در C# 8.0 زمانیکه مینویسم 1^، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var index = new Index(value: 1, fromEnd: true); Index indexStruct = ^1; var indexShortHand = ^1;
در سطر اول، پارامتر fromEnd نیز قابل تعریف است. این fromEnd با مقدار true، همان عملگر ^ در اینجا است و عدم ذکر این عملگر به معنای false بودن آن است:
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Console.WriteLine(a[a.Length – 2]); // will write 8 on console. Console.WriteLine(a[^2]); // will write 8 on console. Index i5 = 5; Console.WriteLine(a[i5]); //will write 5 on console. Index i2fromEnd = ^2; Console.WriteLine(a[i2fromEnd]); // will write 8 on console.
روش دسترسی به بازهای از اعضای یک آرایه تا پیش از C# 8.0
فرض کنید آرایهای از اعداد بین 1 تا 10 را به صورت زیر ایجاد کردهاید:
var numbers = Enumerable.Range(1, 10).ToArray();
var (start, end) = (1, 7); var length = end - start; // Using LINQ var subset1 = numbers.Skip(start).Take(length); // Or using Array.Copy var subset2 = new int[length]; Array.Copy(numbers, start, subset2, 0, length);
روش دسترسی به بازهای از اعضای یک آرایه در C# 8.0
در C# 8.0 برای دسترسی به بازهای از عناصر یک آرایه میتوان از range expression که به صورت x..y نوشته میشود، استفاده کرد. در ادامه، مثالهایی را از کاربردهای عبارت .. ملاحظه میکنید:
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" };
var fromIndexToX = myArray[1..3]; // = [Item2, Item3]
var fromIndexToXFromTheEnd = myArray[1..^1]; // = [ "Item2", "Item3", "Item4" ]
var fromAnIndexToTheEnd = myArray[1..]; // = [ "Item2", "Item3", "Item4", "Item5" ]
var fromTheStartToAnIndex = myArray[..3]; // = [ "Item1", "Item2", "Item3" ]
var entireRange = myArray[..]; // = [ "Item1", "Item2", "Item3", "Item4", "Item5" ]
مثالی دیگر: بازنویسی یک حلقهی for با foreach
حلقهی for زیر را
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" }; for (int i = 1; i <= 3; i++) { Console.WriteLine(myArray[i]); }
foreach (var item in myArray[1..4]) // = [ "Item2", "Item3", "Item4" ] { Console.WriteLine(item); }
یعنی ابتدای آن inclusive است و انتهای آن exclusive
چند مثال کاربردی و متداول از بازهها
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Main() { var skip2CharactersAndTake2Characters = _numbers[2..4]; // صرفنظر کردن از دو عنصر اول و سپس انتخاب دو عنصر var skipFirstAndLastCharacter = _numbers[1..^1]; // صرفنظر کردن از دو عنصر اول و آخر var last3Characters = _numbers[^3..]; // انتخاب بازهای شامل سه عنصر آخر var first4Characters = _numbers[0..4]; // دریافت بازهای از 4 عنصر اول var rangeStartFrom2 = _numbers[2..]; // دریافت بازهای شروع شده از المان دوم تا آخر var skipLast3Characters = _numbers[..^3]; // صرفنظر کردن از سه المان آخر var rangeAll = _numbers[..]; // انتخاب کل بازه } } }
معرفی نوع جدید Range
در C# 8.0 زمانیکه مینویسم 4..1، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var range = new Range(1, 4); Range rangeStruct = 1..4; var rangeShortHand = 1..4;
یک مثال: استفاده از نوع جدید Range به عنوان پارامتر یک متد
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Print(Range range) => Console.WriteLine($"{range} => {string.Join(", ", _numbers[range])}"); static void Main() { Print(1..3); // 1..3 => 2, 3 Print(..3); // 0..3 => 1, 2, 3 Print(3..); // 3..^0 => 4, 5, 6, 7, 8, 9, 10 Print(1..^1); // 1..^1 => 2, 3, 4, 5, 6, 7, 8, 9 Print(^2..^1); // ^2..^1 => 9 } } }
مثالی دیگر: استفاده از Range به عنوان جایگزینی برای متد String.Substring
از Range میتوان برای کار بر روی رشتهها و انتخاب قسمتی از آنها نیز استفاده کرد:
Console.WriteLine("123456789"[1..4]); // Would output 234
var helloWorldStr = "Hello, World!"; var hello = helloWorldStr[..5]; Console.WriteLine(hello); // Output: Hello var world = helloWorldStr[7..]; Console.WriteLine(world); // Output: World! var world2 = helloWorldStr[^6..]; // Take the last 6 characters Console.WriteLine(world); // Output: World!
سؤال: زمانیکه بازهای از یک آرایه را انتخاب میکنیم، آیا یک آرایهی جدید ایجاد میشود، یا هنوز به همان آرایهی قبلی اشاره میکند؟
پاسخ: یک آرایهی جدید ایجاد میشود؛ اما میتوان با فراخوانی متد ()array.AsSpan پیش از انتخاب یک بازه، بازهای را تولید کرد که دقیقا به همان آرایهی اصلی اشاره میکند و یک کپی جدید نیست:
var arr = (new[] { 1, 4, 8, 11, 19, 31 }).AsSpan(); var range = arr[2..5]; ref int elt1 = ref range[1]; elt1 = -1; int copiedElement = range[2]; copiedElement = -2; Console.WriteLine($"range[1]: {range[1]}, range[2]: {range[2]}"); // output: range[1]: -1, range[2]: 19 Console.WriteLine($"arr[3]: {arr[3]}, arr[4]: {arr[4]}"); // output: arr[3]: -1, arr[4]: 19
1) بهروز رسانی شماره نگارش داتنت
اولین قدم در جهت ارتقاء پروژههای قدیمی، تغییر شماره نگارش TargetFramework موجود در فایل csproj. به net8.0 است. پس از اینکار نیاز است تمام بستههای نیوگت موجود را نیز به نگارشهای جدیدتر آنها ارتقاء دهید.
2) فعالسازی حالت SSR تعاملی سمت سرور
پایهی تمام تغییرات انجام شدهی در Blazor 8x، قابلیت SSR است و تمام امکانات دیگر برفراز آن اجرا میشوند. به همین جهت پس از ارتقاء شماره نگارش داتنت، نیاز است SSR را فعال کنیم و برای اینکار باید به هاست ASP.NET Core بگوئیم که درخواستهای رسیده را به کامپوننتهای Razor هدایت کند. بنابراین، به فایل Program.cs مراجعه کرده و دو تغییر زیر را به آن اعمال کنید:
// ... builder.Services.AddRazorComponents().AddInteractiveServerComponents(); // ... app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
در اینجا ترکیب کامپوننتهای تعاملی سمت سرور (AddInteractiveServerComponents) و رندر تعاملی سمت سرور (AddInteractiveServerRenderMode)، دقیقا همان Blazor Server قدیمی است که ما با آن آشنا هستیم.
یک نکته: اگر از قالب جدید dotnet new blazor --interactivity None استفاده کنیم، یعنی حالت تعاملی بودن آنرا به None تنظیم کنیم، کلیات ساختار پروژهای را که مشاهده خواهیم کرد، با حالت تعاملی Server آن یکی است؛ فقط در تنظیمات Program.cs آن، گزینههای فوق را نداریم و به صورت زیر ساده شدهاست:
// ... builder.Services.AddRazorComponents(); // ... app.MapRazorComponents<App>();
3) ایجاد فایل جدید App.razor
در دات نت 8، دیگر خبری از فایل آغازین Host.cshtml_ پروژههای Blazor Server قدیمی نیست و کدهای آن با تغییراتی، به فایل جدید App.razor منتقل شدهاند. در این قسمت، کار هدایت درخواستهای رسیده به کامپوننتهای برنامه رخ میدهد و از این پس، صفحهی ریشهی برنامه خواهد بود.
در این تصویر، مقایسهای را بین جریان پردازش یک درخواست رسیده در دات نت 8، با نگارش قبلی Blazor Server مشاهده میکنید. در دات نت 8، فایل Host.cshtml_ (یک Razor Page آغازین برنامه) با یک کامپوننت Razor به نام App.razor جایگزین شدهاست و فایل قدیمی App.razor این پروژهها به Routes.razor، تغییر نام یافتهاست.
نمونهای از فایل App.razor جدید را که در قسمت قبل نیز معرفی کردیم، در اینجا با جزئیات بیشتری بررسی میکنیم:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="MyApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.png" /> <HeadOutlet /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> </body> </html>
- تمام دایرکتیوهای تعریف شده مانند page ،@addTagHelper@ و غیره حذف شدهاند.
- base href تعریف شده اینبار فقط با یک / شروع میشود و نه با /~. این مورد خیلی مهم است! اگر به آن دقت نکنید، هیچکدام از فایلهای استاتیک برنامه مانند فایلهای css. و js.، بارگذاری نخواهند شد!
- پیشتر برای رندر HeadOutlet، از یک تگهلپر استفاده میشد. این مورد در نگارش جدید با یک کامپوننت ساده جایگزین شدهاست.
- تمام component tag helperهای پیشین حذف شدهاند و نیازی به آنها نیست.
- ارجاع پیشین فایل blazor.server.js با فایل جدید blazor.web.js جایگزین شدهاست.
یک نکته: همانطور که مشاهده میکنید، فایل App.razor یک کامپوننت است و اینبار به همراه تگ <script> نیز شدهاست. یعنی در این نگارش از Blazor میتوان اسکریپتها را در کامپوننتها نیز ذکر کرد؛ فقط با یک شرط! این کامپوننت حتما باید SSR باشد. اگر این تگ اسکریپتی را در یک کامپوننت تعاملی ذکر کنید، همانند قابل (و نگارشهای پیشین Blazor) با خطا مواجه خواهید شد.
4) ایجاد فایل جدید Routes.razor و مدیریت سراسری خطاها و صفحات یافت نشده
همانطور که عنوان شد، فایل قدیمی App.razor این پروژهها به Routes.razor تغییر نام یافتهاست که درج آنرا در قسمت body مشاهده میکنید. محتوای این فایل نیز به صورت زیر است:
<Router AppAssembly="@typeof(Program).Assembly"> <Found Context="routeData"> <RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> </Router>
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
@page "/StatusCode/{responseCode}" <h3>StatusCode @ResponseCode</h3> @code { [Parameter] public string? ResponseCode { get; set; } }
یک نکته: اگر پروژهای را بر اساس قالب dotnet new blazor --interactivity Server ایجاد کنیم، در فایل Program.cs آن، چنین تنظیمی اضافه شدهاست:
if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error", createScopeForErrors: true); }
سؤال: در اینجا (برنامههای Blazor Server) چه تفاوتی بین UseExceptionHandler و UseStatusCodePagesWithRedirects وجود دارد؟
میانافزار UseExceptionHandler برای مدیریت استثناءهای آغازین برنامه، پیش از تشکیل اتصال دائم SignalR وارد عمل میشود. پس از آن و تشکیل اتصال وبسوکت مورد نیاز، فقط از میانافزار UseStatusCodePagesWithRedirects استفاده میکند.
اگر علاقمند نیستید تا تمام خطاهای رسیده را همانند مثال فوق در یک صفحه مدیریت کنید، میتوانید حداقل سه فایل زیر را به برنامه اضافه کنید تا خطاهای متداول یافت نشدن آدرسی، بروز خطایی و یا عدم دسترسی را مدیریت کنند:
404.razor
@page "/StatusCode/404" <PageTitle>Not found</PageTitle> <h1>Not found</h1> <p role="alert">Sorry, there's nothing at this address.</p>
500.razor
@page "/StatusCode/500" <PageTitle>Unexpected error</PageTitle> <h1>Unexpected error</h1> <p role="alert">There was an unexpected error.</p>
401.razor
@page "/StatusCode/401" <PageTitle>Not Authorized</PageTitle> <h1>Not Authorized</h1> <p role="alert">Sorry, you are not authorized to access this page.</p>
5) تعاملی کردن سراسری برنامه
پس از این تغییرات اگر برنامه را اجرا کنید، بر اساس روش جدید static server-side rendering کار میکند و تعاملی نیست. یعنی تمام کامپوننتهای آن به صورت پیشفرض، یکبار بر روی سرور رندر شده و خروجی آنها به مرورگر کاربر ارسال میشوند و هیچ اتصال دائم SignalR ای برقرار نخواهد شد. برای فعالسازی سراسری قابلیتهای تعاملی برنامه و بازگشت به حالت Blazor Server قبلی، به فایل App.razor مراجعه کرده و دو تغییر زیر را اعمال کنید تا به صورت خودکار به تمام زیرکامپوننتها، یعنی کل برنامه، اعمال شود:
<HeadOutlet @rendermode="@InteractiveServer" /> ... <Routes @rendermode="@InteractiveServer" />
نکته 1: اجرای دستور زیر در داتنت 8، قالب پروژهای را ایجاد میکند که رفتار آن همانند پروژههای Blazor Server نگارشهای قبلی داتنت است (این مورد را در قسمت قبل بررسی کردیم)؛ یعنی همهجای آن به صورت پیشفرض، تعاملی است:
dotnet new blazor --interactivity Server --all-interactive
نکته 2: البته ... InteractiveServer، دقیقا همان حالت پیشفرض برنامههای Blazor Server قبلی نیست! این حالت رندر، به صورت پیشفرض به همراه پیشرندر (pre-rendering) هم هست. یعنی در این حالت، روال رویدادگردان OnInitializedAsync یک کامپوننت، دوبار فراخوانی میشود (که باید به آن دقت داشت و عدم توجه به آن میتواند سبب انجام دوبارهی کارهای سنگین آغازین یک کامپوننت شود)؛ یکبار برای پیشرندر صفحه به صورت یک HTML استاتیک (بدون فعال سازی هیچ قابلیت تعاملی) که برای موتورهای جستجو و بهبود SEO مفید است و بار دیگر برای فعالسازی قسمتهای تعاملی آن، درست پس از زمانیکه اتصال SignalR صفحه، برقرار شد (البته امکان فعالسازی حالت پیشرندر در Blazor Server قبلی هم وجود داشت؛ ولی مانند Blazor 8x، به صورت پیشفرض فعال نبود). در صورت نیاز، برای سفارشی سازی و لغو آن میتوان به صورت زیر عمل کرد:
@rendermode InteractiveServerRenderModeWithoutPrerendering @code{ static readonly IComponentRenderMode InteractiveServerRenderModeWithoutPrerendering = new InteractiveServerRenderMode(false); }
در مورد پیشرندر و روش مدیریت دوبار فراخوانی شدن روال رویدادگردان OnInitializedAsync یک کامپوننت در این حالت، در قسمتهای بعدی این سری بیشتر بحث خواهد شد.
همچنین مزیت دیگر آن، انتقال سادهتر کدهای جاوا به سیشارپ است؛ از این لحاظ که ویژگی مشابهی در زبان جاوا تحت عنوان «Default Methods» سالها است که وجود دارد.
یک مثال از ویژگی «پیاده سازیهای پیشفرض در اینترفیسها»
interface ILogger { void Log(string message); } class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } }
مدتی بعد بر اساس نیازمندیهای مشخصی به این نتیجه خواهید رسید که بهتر است overload دیگری را برای متد Log در اینترفیس ILogger، درنظر بگیریم. مشکلی که این تغییر به همراه دارد، کامپایل نشدن کلاس ConsoleLogger در یک برنامهی ثالث است و این کلاس باید الزاما این overload جدید را پیاده سازی کند؛ در غیراینصورت قادر به کامپایل برنامهی خود نخواهد شد. اکنون در C# 8.0 میتوان برای این نوع تغییرات، در همان اینترفیس اصلی، یک پیاده سازی پیشفرض را نیز قرار داد:
interface ILogger { void Log(string message); void Log(Exception exception) => Console.WriteLine(exception); }
ویژگی «پیاده سازیهای پیشفرض در اینترفیسها» چگونه پیاده سازی شدهاست؟
واقعیت این است که امکان پیاده سازی این ویژگی، سالها است که در سطح کدهای IL دات نت وجود داشته (از زمان دات نت 2) و اکنون از طریق کدهای برنامه با بهبود کامپایلر آن، قابل دسترسی شدهاست.
تاثیر زمینهی کاری بر روی دسترسی به پیاده سازیهای پیشفرض
مثال زیر را درنظر بگیرید:
interface IDeveloper { void LearnNewLanguage(string language, DateTime dueDate); void LearnNewLanguage(string language) { // default implementation LearnNewLanguage(language, DateTime.Now.AddMonths(6)); } } class BackendDev : IDeveloper // compiles OK { public void LearnNewLanguage(string language, DateTime dueDate) { // Learning new language... } }
سؤال: به نظر شما اکنون کدامیک از کاربردهای زیر از کلاس BackendDev، کامپایل میشود و کدامیک خیر؟
IDeveloper dev1 = new BackendDev(); dev1.LearnNewLanguage("Rust"); var dev2 = new BackendDev(); dev2.LearnNewLanguage("Rust");
There is no argument given that corresponds to the required formal parameter 'dueDate' of 'BackendDev.LearnNewLanguage(string, DateTime)' (CS7036) [ConsoleApp]
ارثبری چندگانه چطور؟
احتمالا حدس زدهاید که این قابلیت ممکن است ارثبری چندگانه را که در سیشارپ ممنوع است، میسر کند. تا C# 8.0، یک کلاس تنها از یک کلاس دیگر میتواند مشتق شود؛ اما این محدودیت در مورد اینترفیسها وجود ندارد. به علاوه تاکنون اینترفیسها مانند کلاسها، امکان تعریف پیاده سازی خاصی را نداشتند و صرفا یک قرارداد بیشتر نبودند. بنابراین اکنون این سؤال مطرح میشود که آیا میتوان با ارائهی پیاده سازی پیشفرض متدها در اینترفیسها، ارثبری چندگانه را در سیشارپ پیاده سازی کرد؛ مانند مثال زیر؟!
using System; namespace ConsoleApp { public interface IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a default way."); } public interface IBackendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a backend way."); } public interface IFrontendDev : IDev { void LearnNewLanguage(string language) => Console.Write($"Learning {language} in a frontend way."); } public interface IFullStackDev : IBackendDev, IFrontendDev { } public class Dev : IFullStackDev { } }
IFullStackDev dev = new Dev(); dev.LearnNewLanguage("TypeScript");
The call is ambiguous between the following methods or properties: 'IBackendDev.LearnNewLanguage(string)' and 'IFrontendDev.LearnNewLanguage(string)' (CS0121)
تفاوت امکانات کلاسهای Abstract با متدهای پیشفرض اینترفیسها چیست؟
اینترفیسها هنوز نمیتوانند مانند کلاسها، سازندهای را تعریف کنند. نمیتوانند متغیرها/فیلدهایی را در سطح اینترفیس داشته باشند. همچنین در اینترفیسها همهچیز public است و امکان تعریف سطح دسترسی دیگری وجود ندارد.
بنابراین باید بخاطر داشت که هدف از تعریف اینترفیسها، ارائهی «یک رفتار» است و هدف از تعریف کلاسها، ارائه «یک حالت».
یک نکته: در نگارشهای پیش از C# 8.0 هم میتوان ویژگی «متدهای پیشفرض» را شبیه سازی کرد
واقعیت این است که توسط ویژگی «متدهای الحاقی»، سالها است که امکان افزودن «متدهای پیشفرضی» به اینترفیسها در زبان سیشارپ وجود دارد:
namespace MyNamespace { public interface IMyInterface { IList<int> Values { get; set; } } public static class MyInterfaceExtensions { public static int CountGreaterThan(this IMyInterface myInterface, int threshold) { return myInterface.Values?.Where(p => p > threshold).Count() ?? 0; } } }
var myImplementation = new MyInterfaceImplementation(); // Note that there's no typecast to IMyInterface required var countGreaterThanFive = myImplementation.CountGreaterThan(5);
NET 5.0 Preview 8. منتشر شد
Today, we are releasing .NET 5.0 Preview 8. The .NET 5.0 release is now “feature complete”, meaning that very nearly all features are in their final form (with the exception of bug fixes still to come). Preview 8 is, appropriately, the last preview. We plan on releasing two go-live release candidates before the final .NET 5.0 release in November. This post describes a selection of features across the .NET 5.0 release.