اشتراک‌ها
خود آموز entityframework

Welcome to EntityFrameworkTutorial.net. Here you will learn everything about Entity Framework in easy steps. It covers basic to expert level tutorials for all the features of Entity Framework with code snippets. Readers can also download sample project and the School database used in various tutorials. 

خود آموز entityframework
اشتراک‌ها
استفاده از GitHub Copilot برای بهبود کیفیت کدنویسی
How to Level Up Your Coding with GitHub Copilot?

TL;DR: AI coding tools like GitHub Copilot are revolutionizing development by offering smart code suggestions, error detection, and seamless integration with popular IDEs. With 70% of developers embracing AI for increased productivity, you can boost your coding efficiency and focus on creative solutions with AI assistance.
استفاده از GitHub Copilot برای بهبود کیفیت کدنویسی
مطالب
بررسی تغییرات Blazor 8x - قسمت سوم - روش ارتقاء برنامه‌های Blazor Server قدیمی به دات نت 8
در قسمت قبل، با نحوه‌ی رندر سمت سرور و روش فعالسازی قابلیت‌های تعاملی در این حالت، آشنا شدیم. از این نکات می‌توان جهت ارتقاء ساختار پروژه‌های قدیمی Blazor Server به Blazor Server 8x استفاده کرد. البته همانطور که پیشتر نیز عنوان شد، در دات نت 8 دیگر خبری از قالب‌های قدیمی پروژه‌های blazor server و blazor wasm نیست و اگر دقیقا همین موارد مدنظر هستند، آن‌ها را می‌توان با تنظیم سطح رندر و میزان تعاملی که مدنظر است، شبیه سازی کرد و یا حتی هر دو را هم با هم در یک پروژه داشت.


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();
یک نمونه‌ی کامل از فایل Program.cs را در قسمت قبل مشاهده کردید و یا حتی می‌توانید دستور dotnet new blazor --interactivity Server را جهت ساخت یک پروژه‌ی آزمایشی جدید بر اساس SDK دات نت 8 و ایده گیری از آن، اجرا کنید.

در اینجا ترکیب کامپوننت‌های تعاملی سمت سرور (AddInteractiveServerComponents) و رندر تعاملی سمت سرور (AddInteractiveServerRenderMode)، دقیقا همان Blazor Server قدیمی است که ما با آن آشنا هستیم.

یک نکته: اگر از قالب جدید dotnet new blazor --interactivity None استفاده کنیم، یعنی حالت تعاملی بودن آن‌را به None تنظیم کنیم، کلیات ساختار پروژه‌ای را که مشاهده خواهیم کرد، با حالت تعاملی Server آن یکی است؛ فقط در تنظیمات Program.cs آن، گزینه‌های فوق را نداریم و به صورت زیر ساده شده‌است:
// ...

builder.Services.AddRazorComponents();

// ...

app.MapRazorComponents<App>();
در این نوع برنامه‌ها نمی‌توان جزایر/قسمت‌های تعاملی Blazor Server را در صفحات و کامپوننت‌های SSR، تعریف کرد. در مورد جزایر تعاملی، در مطالب بعدی بیشتر بحث خواهیم کرد.


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>
این محتوای جدید، فاقد ذکر کامپوننت NotFound قبلی است؛ از این جهت که سیستم مسیریابی جدید Blazor 8x با ASP.NET Core 8x یکپارچه است و نیازی به این کامپوننت نیست. یعنی اگر قصد مدیریت آدرس‌های یافت نشده‌ی برنامه را دارید، باید همانند ASP.NET Core به صورت زیر در فایل Program.cs عمل کرده:
app.UseStatusCodePagesWithRedirects("/StatusCode/{0}");
و سپس کامپوننت جدید StatusCode.razor را برای مدیریت آن به نحو زیر به برنامه اضافه کنید و بر اساس responseCode دریافتی، واکنش‌های متفاوتی را ارائه دهید:
@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);
}
که دقیقا معادل رفتاری است که در برنامه‌های ASP.NET Core قابل مشاهده‌است. این مسیر Error/، به کامپوننت جدید Components\Pages\Error.razor نگاشت می‌شود. بنابراین اگر در برنامه‌های جدید Blazor Server، استثنائی رخ دهد، با استفاده از میان‌افزار ExceptionHandler فوق، کامپوننت Error.razor نمایش داده خواهد شد.  باید دقت داشت که این کامپوننت ویژه، تحت هر شرایطی در حالت یک static server component رندر می‌شود.

سؤال: در اینجا (برنامه‌های 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 یک کامپوننت در این حالت، در قسمت‌های بعدی این سری بیشتر بحث خواهد شد.
 
مطالب
درخت‌ها و گراف قسمت چهارم
در قسمت قبلی مبحث پیاده سازی ساختمان (ساختار) درخت‌های جستجوی دودویی را به پایان رساندیم. در این قسمت قرار است بر روی درخت متوازن بحث کنیم و آن را پیاده سازی نماییم.

درخت متوازن
همانطور که دیدید، عملیات جستجو  روی درخت جستجوی دو دویی به مراتب راحت و آسان‌تر است؛ ولی با این حال این درخت در عملیاتی چون درج و حذف، یک نقص فنی دارد و آن هم این است که نمی‌تواند عمق خود را کنترل کند و همینطور به سمت عمق‌های بیشتر و بزرگتر حرکت می‌کند. مثلا ساختار ترتیبی زیر را برای مقداری‌های 1 و 2 و 3 و 4 و  5و 6 در نظر بگیرید:

در این حالت دیگر درخت مانند یک درخت رفتار نمی‌کند و بیشتر شبیه لیست پیوندی است و عملیات جستجو همینطور کندتر و کندتر می‌شود و دیگر مثل سابق نخواهد بود، پیچیدگی برنامه بیشتر خواهد شد و از (Log(n به n می‌رسد. از آنجا که دوست داریم برای عملیات‌های رایجی چون درج و جستجو و حذف، همین پیچیدگی لگاریتمی را حفظ کنیم، از ساختاری جدیدتر بهره خواهیم برد.

درخت دودویی متوازن:  درختی است که در آن هیچ برگی، عمقش از هیچ برگی بیشتر نیست.

درخت دودویی متوازن کامل: درختی که تفاوتش در تعداد گره‌های چپ یا راست است و حداقل یک فرزند دارند.

درخت دودویی متوازن حتی اگر کامل هم نباشد، در عملیات پایه‌ای چون افزودن، حذف و جستجو در بدترین حالت هم با پیچیدگی لگاریتمی تعداد گام‌ها همراه است. برای اینکه این درخت با به هم ریختگی توازنش روبرو نشود، باید حین انجام عملیات پایه، جایگاه تعداد المان‌های آن بررسی و اصلاح شود که به این عملیات چرخش یا دوران Rotation می‌گویند. انجام این عملیات بستگی دارد که پیاده سازی این درخت به چه شکلی باشد و به چه صورتی پیاده سازی شده باشد. از پیاده سازی‌های این درخت می‌توان به درخت سرخ-سیاه Black Red Tree ، ای وی ال AVL Tree ، اسپلی Splay و ... اشاره کرد.

با توجه به موارد بالا میتوانیم به نتایج زیر برسیم که چرا این درخت در هر حالت، پیچیدگی زمانی خودش را در لگاریتم n حفظ می‌کند:

  • المان‌ها و عناصرش را مرتب شده نگه می‌دارد.
  • خودش را متوازن نگه داشته و اجازه نمی‌دهد عمقش بیشتر از لگاریتم n شود.

نمونه ای از درخت جستجوی دو دویی

همان درخت ولی به صورت متوازن با پیاده سازی AVL


درخت‌های متوازن هم می‌متوانند دو دویی یا باینری باشند و هم غیر باینری non-Binary

درخت‌های دو دویی در انجام عملیات بسیار سریع هستند و در بالا به تعدادی از انواع پیاده سازی‌های آن اشاره کردیم.

درخت‌های غیر باینری نیز مرتب و متوازن بوده، ولی می‌توانند بیش از یک کلید داشته باشند و همچنین بیشتر از دو فرزند. این درخت‌ها نیز عملیات خود را بسیار سریع انجام می‌دهند. از نمونه‌های این درخت می‌توان به B Tree,B+ Tree,Interval Tree اشاره کرد.

از آنجا که پیاده‌سازی این نوع درخت کمی دشوار و پیچیده و طولانی است و همچنین پیاده سازی‌های مختلفی دارد؛ تعدادی از منابع موجود را در زیر معرفی می‌کنیم:

در خود دات نت در فضای نام system.collection.generic کلاس TreeSet یک نوع پیاده سازی از این درخت است که این پیاده سازی از نوع درخت سرخ سیاه می‌باشد و جالب است بدانید، در طی بیست گام می‌تواند در یک میلیون آیتم به جستجو بپردازد ولی خبر بد اینکه استفاده مستقیم از این کلاس ممکن نیست چرا که این کلاس به صورت داخلی internal برای استفاده‌ی خود کتابخانه طراحی شده است ولی خبر خوب اینکه کلاس sortedDictionary از این کلاس بهره برده است و به صورت عمومی در دسترس ما قرار گرفته است. همچنین کلاس SortedSet هم از دات نت 4  نیز در دسترس است.

 

کتابخانه‌های خارجی جهت استفاده در دات نت که به پیاده‌سازی درخت‌های متوازن پرداخته‌اند:

پیاده سازی Splay Tree با سی شارپ 

پیاده سازی AVL به صورت بهینه و آسان برای استفاده

پیاده سازی درخت AVL با کارایی بالا

پیاده سازی درخت AVL به همراه نمایش گرافیکی آن

درخت سرخ-سیاه

کتابخانه ای متن باز برای درخت سرخ-سیاه

درخت متوازن به همراه جست و جو و حذف و پیمایش ها

غیر دودویی‌ها

پیاده سازی B Tree

پیاده سازی Interval Tree 
پیاده سازی Interval Tree در سی ++  
مطالب
آشنایی با کلاس JavaScriptSerializer

برای استفاده از jQuery Ajax یکی از روش‌های ارسال دیتا به برنامه، تبدیل داده‌ها به فرمت JSON می‌باشد. برای داده‌های ساده، تشخیص این فرمت ساده است. مثلا اگر امضای تابع وب سرویس اجکس ما به صورت زیر باشد:
public static bool IsUserAvailable(string username)
اطلاعات جی‌سونی را که قرار است ارسال کنیم، فرمت زیر را باید داشته باشد:
{'username':'value'}
حال اگر آرگومان‌های ما پیچیده‌تر بودند چطور؟ مثلا بجای یک رشته ساده، یک لیست جنریک داشتیم، فرمت ورودی را چگونه باید تشخیص داد؟
برای این منظور در دات نت 3 و نیم، کلاسی جهت انجام اینگونه تبدیلات پیش بینی شده است که شرح مختصر آن به صورت زیر است:
ابتدا باید ارجاعی را به اسمبلی system.web.extensions به برنامه افزود و سپس جهت سهولت کار می‌توان یک extension method از کلاس JavaScriptSerializer مهیا در فضای نام System.Web.Script.Serialization ایجاد کرد:

public static string ToJson(this object data)
{
return new JavaScriptSerializer().Serialize(data);
}
اکنون چند مثال زیر را در نظر بگیرید:
        public static string GetJsonTest0()
{
var data = "a1";
return data.ToJson();
}

public static string GetJsonTest1()
{
var data = new List<string> { "a1", "a2", "a3" };
return data.ToJson();
}

public static string GetJsonTest2()
{

var lst =
new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("Name", "علی"),
new KeyValuePair<string, object>("Number", 10),
new KeyValuePair<string, object>("Desc", "منابع مورد نیاز")
};

return lst.ToJson();

}
خروجی‌های آن‌ها به ترتیب به صورت زیر خواهند بود:

"a1"
["a1","a2","a3"]
[{"Key":"Name","Value":"علی"},{"Key":"Number","Value":10},{"Key":"Desc","Value":"منابع مورد نیاز"}]

این کلاس همچنین قابلیت Deserialize و تبدیل داده‌هایی به فرمت JSON به اشیاء مورد نظر ما را نیز دارا است.

مطالب
تغییرات Logging در ASP.NET Core 6x
فرض کنید با استفاده از روش متداول زیر، کار ثبت یک واقعه را انجام داده‌اید:
public class TestController
{
    private readonly ILogger<TestController> _logger;
    public TestController(ILogger<TestController> logger)
    {
        _logger = logger;
    }

   [HttpGet("/")]
    public string Get()
    {
        _logger.LogInformation("hello world");
          return "Hello world!";
    }
}
در یک برنامه‌ی متداول ASP.NET Core، زیرساخت کار با ILogger از پیش تنظیم شده‌است. برای کار با آن فقط کافی است به نمونه‌های ILogger و یا <ILogger<T از طریق سیستم تزریق وابستگی‌ها دسترسی یافت و سپس متدهای الحاقی آن‌را مانند LogInformation فراخوانی کرد.

اگر یک چنین برنامه‌ای را به دات نت 6 ارتقاء دهید، با پیام اخطار زیر مواجه خواهید شد:
CA1848: For improved performance, use the LoggerMessage delegates instead of calling LogInformation
به صورت خلاصه، تمام متدهای پیشین LogInformation، LogDebug و امثال آن در دات نت 6 منسوخ شده درنظر گرفته می‌شوند! دلیل آن‌را در ادامه بررسی خواهیم کرد.


استفاده‌ی گسترده از source generators در دات نت 6

source generators، امکان مداخله در عملیات کامپایل برنامه را میسر کرده و امکان تولید کدهای پویایی را در زمان کامپایل، فراهم می‌کنند. هرچند این قابلیت به همراه دات نت 5 ارائه شدند، اما تا زمان دات نت 6 استفاده‌ی گسترده‌ای از آن در خود دات نت صورت نگرفت. موارد زیر، تغییراتی است که بر اساس source generators در دات نت 6 رخ داده‌اند:
- source generators مخصوص ILogger (موضوع این بحث؛ یعنی LoggerMessage source generator)
- source generators مخصوص System.Text.Json تا سربار تبدیل به JSON و یا برعکس کمتر شود.
- بازنویسی مجدد پروسه‌ی کامپایل Blazor/Razor بر اساس source generators، بجای روش دو مرحله‌ای قبلی که امکان Hot Reload را فراهم کرده‌است.

نوشتن یک source generator هرچند ساده نیست، اما چون نیاز به reflection را به حداقل می‌رساند، می‌تواند تغییرات کارآیی بسیار مثبتی را به همراه داشته باشد.


توصیه به استفاده از LoggerMessage.Define در دات نت 6

ILogger به همراه قابلیت‌هایی مانند structural logging نیز هست که امکان فرمت بهتر پیام‌های ثبت شده را میسر می‌کند تا توسط برنامه‌های جانبی که قرار است این لاگ‌ها را پردازش کنند، به سادگی قابل خواندن باشند. برای مثال رکورد زیر را در نظر بگیرید:
public record Person (int Id, string Name);
به همراه نمونه‌ای از آن:
var person = new Person(123, "Test");
خروجی لاگ زیر در این حالت:
_logger.LogInformation("hello to {Person}", person);
به صورت زیر خواهد بود:
info: TestController[0]
hello world to Person { Id = 123, Name = Test }
دقت کنید که رشته‌ی ارسالی به LogInformation به همراه $ نیست. یعنی از string interpolation استفاده نشده‌است و نام پارامتر تعریف شده (placeholder name) با حروف بزرگ شروع شده‌است.

اگر در اینجا مانند مثال زیر از string interpolation استفاده شود:
_logger.LogInformation($"hello world to {person}"); // Using interpolation instead of structured logging
هرچند کار با آن ساده‌تر است از string.Format، اما برای عملیات ثبت وقایع با کارآیی بالا توصیه نمی‌شود؛ به این دلایل:
- ویژگی «لاگ‌های ساختار یافته» را از دست می‌دهیم و دیگر توسط نرم افزارهای ثالث لاگ خوان، به سادگی پردازش نخواهند شد.
- ویژگی «قالب ثابت» پیام را نیز از دست خواهیم داد که باز هم یافتن پیام‌های مشابه را در بین انبوهی از لاگ‌های رسیده مشکل می‌کند.
-  کار serialization شیء ارسالی به آن، پیش از عملیات ثبت وقایع رخ می‌دهد. اما ممکن است سطح لاگ سیستم در این حد نباشد و اصلا این پیام لاگ نشود. در این حالت یک کار اضافی صورت گرفته و بر روی کارآیی برنامه تاثیر منفی خواهد گذاشت.

برای جلوگیری از serialization و همچنین تخصیص حافظه‌ی اضافی و مشکلات عدم ساختار یافته بودن لاگ‌ها، توصیه شده‌است که ابتدا سطح لاگ مدنظر بررسی شود و همچنین از string interpolation استفاده نشود:
if (_logger.IsEnabled(LogLevel.Information))
{
   _logger.LogInformation("hello world to {Person}", person);
}
البته مشکل این روش، تکرار این if/else‌ها در تمام برنامه‌است و همچنین باید دقت داشت که LogLevel انتخابی، با متد لاگ، هماهنگی دارد.
مشکل دیگر لاگ‌های ساختار یافته، امکان فراموش کردن یکی از پارامترها است که با یک خطای زمان اجرا گوشزد خواهد شد؛ مانند مثال زیر:
_logger.LogInformation("hello world to {Person} because {Reason}", person);
اکنون در دات نت 6 با پیام اخطار CA1848 که در ابتدای بحث مشاهده کردید، توصیه می‌کنند که اگر قالب نهایی خاصی را مدنظر دارید، آن‌را توسط متد LoggerMessage.Define دقیقا مشخص کنید:
private static readonly Action<ILogger, Person, Exception?> _logHelloWorld =
    LoggerMessage.Define<Person>(
        logLevel: LogLevel.Information,
        eventId: 0,
        formatString: "hello world to {Person}");
در این روش جدید باید یک Action را برای لاگ کردن پیام‌ها تهیه کرد که از همان ابتدا LogLevel آن مشخص است (و نیازی به بررسی مجزا ندارد؛ یعنی خودش logger.IsEnabled را فراخوانی می‌کند) و همچنین از روش لاگ ساختار یافته استفاده می‌کند. مزیت این روش کش شدن قالب لاگ، در بار اول فراخوانی آن است ( برخلاف متدهای الحاقی مانند LogInformation که هربار باید این قالب‌ها را پردازش کنند) و همچنین در اینجا دیگر خبری از boxing و تبدیل نوع پارامترها نیست.

اکنون روش فراخوانی این Action با کارآیی بالا به صورت زیر است:
[HttpGet("/")]
public string Get()
{
    var person = new Person(123, "Test");
    _logHelloWorld(_logger, person, null);
      return "Hello world!";
}
همانطور که مشاهده می‌کنید اینبار دیگر حتی امکان فراموش کردن پارامتری وجود ندارد (مشکلی که می‌تواند با LogInformation متداول رخ دهد).


معرفی [LoggerMessage] source generator در دات نت 6

هرچند LoggerMessage.Define، مزایای قابل توجهی مانند کش شدن قالب لاگ، عدم نیاز به بررسی ضرورت لاگ شدن پیام و ارسال تعداد پارامترهای صحیح را به همراه دارد، اما ... کار کردن با آن مشکل است و برای کار با آن باید کدهای زیادی را نوشت. به همین جهت با استفاده از قابلیت source generators، امکان تولید خودکار این نوع کدها در زمان کامپایل برنامه پیش‌بینی شده‌است:
public partial class TestController
{
   [LoggerMessage(0, LogLevel.Information, "hello world to {Person}")]
   partial void LogHelloWorld(Person person);
}
این قطعه کد، LoggerMessage.Define را به صورت خودکار برای ما تولید می‌کند. برای اینکار باید یک متد partial را تهیه کرد و سپس آن‌را به ویژگی جدید LoggerMessage مزین کرد. پس از آن source generator، مابقی کارها را در زمان کامپایل برنامه انجام می‌دهد.
ویژگی partial method، امکان تعریف یک متد را در یک فایل و سپس ارائه‌ی پیاده سازی آن‌را در فایلی دیگر میسر می‌کند که البته در اینجا آن فایل دیگر، توسط source generator تولید می‌شود.
باید دقت داشت که در اینجا TestController را نیز باید به صورت partial تعریف کرد تا آن نیز قابلیت بسط در چند فایل را پیدا کند. همچنین متد فوق را به صورت static partial void نیز می‌توان نوشت.

یکی از مزایای کار با source generator که خودش در اصل یک آنالایزر هم هست، بررسی تعداد پارامترهای ارسالی و تعریف شده‌است:
[LoggerMessage(0, LogLevel.Information, "hello world to {Person} with a {Reason}")]
partial void LogHelloWorld(Person person);
برای مثال در اینجا متد LogHelloWorld یک پارامتر دارد اما LoggerMessage آن به همراه دو پارامتر تعریف شده‌است که این مشکل در زمان کامپایل تشخیص داده شده و گوشزد می‌شود (برخلاف روش‌های پیشین که در زمان اجرا این نوع مشکلات نمایان می‌شدند).

در این روش، امکان ذکر پارامتر اختیاری LogLevel هم وجود دارد؛ اگر نیاز است مقدار آن به صورت پویا تغییر کند:
[LoggerMessage(Message = "hello world to {Person}")]
partial void LogHelloWorld(LogLevel logLevel, Person person);
مطالب
Blazor 5x - قسمت 27 - برنامه‌ی Blazor WASM - کار با سرویس‌های Web API
در قسمت‌های Blazor Server مثال این سری، با روش کار با سرویس‌های سمت سرور برنامه، آشنا شدیم. در این نوع برنامه‌ها، فقط کافی است اصل سرویس مدنظر را مستقیما در کامپوننت‌های Razor تزریق کرد و سپس می‌توان به نحو متداولی با آن‌ها کار کرد؛ اما در برنامه‌های Blazor WASM خیر! به این نوع برنامه‌های سمت کلاینت باید همانند برنامه‌های React ، Angular ، Vue و یا حتی برنامه‌های مبتنی بر jQuery نگاه کرد. در تمام فناوری‌های سمت کلاینت، این درخواست‌های Ajax ای هستند که با سرویس‌های یک Web API سمت سرور، ارتباط برقرار کرده، اطلاعاتی را به آن‌ها ارسال و یا دریافت می‌کنند. در برنامه‌های Blazor WASM نیز باید به همین ترتیب عمل کرد و در اینجا HttpClient دات نت، جایگزین برای مثال jQuery Ajax ، Fetch API و یا XMLHttpRequest استاندارد می‌شود (البته jQuery Ajax در اصل یک محصور کننده‌ی استاندارد XMLHttpRequest است که برای اولین بار توسط مایکروسافت در برنامه‌ی Outlook web access معرفی شد).


ایجاد سرویس سمت کلاینت دریافت اطلاعات اتاق‌ها از Web API

در قسمت 24، HotelRoomController را تکمیل کردیم که کار آن، بازگشت اطلاعات تمام اتاق‌ها و یا یک اتاق مشخص به کلاینت است. اکنون می‌خواهیم در ادامه‌ی قسمت قبل، اگر کاربری بر روی دکمه‌ی Go صفحه‌ی اول رزرو اتاقی کلیک کرد، لیست تمام اتاق‌های تعریف شده را به او نمایش دهیم. به همین جهت نیاز به سرویس سمت کلاینتی داریم که بتواند با این Web API endpoint کار کند:
namespace BlazorWasm.Client.Services
{
    public interface IClientHotelRoomService
    {
        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate);
        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate);
    }
}
این سرویس را در پوشه‌ی Services پروژه‌ی BlazorWasm.Client ایجاد کرده‌ایم که HotelRoomDTO خود را از پروژه‌ی BlazorServer.Models دریافت می‌کند. به این ترتیب می‌توان مدلی را بین یک Web API سمت سرور و یک سرویس سمت کلاینت، به اشتراک گذاشت. بنابراین پروژه‌ی کلاینت، باید ارجاعی را به پروژه‌ی BlazorServer.Models.csproj نیز داشته باشد.

در ادامه اینترفیس فوق را به صورت زیر پیاده سازی می‌کنیم:
namespace BlazorWasm.Client.Services
{
    public class ClientHotelRoomService : IClientHotelRoomService
    {
        private readonly HttpClient _httpClient;

        public ClientHotelRoomService(HttpClient httpClient)
        {
            _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        }

        public Task<HotelRoomDTO> GetHotelRoomDetailsAsync(int roomId, DateTime checkInDate, DateTime checkOutDate)
        {
            throw new NotImplementedException();
        }

        public Task<IEnumerable<HotelRoomDTO>> GetHotelRoomsAsync(DateTime checkInDate, DateTime checkOutDate)
        {
            // How to url-encode query-string parameters properly
            var uri = new UriBuilderExt(new Uri(_httpClient.BaseAddress, "/api/hotelroom"))
                            .AddParameter("checkInDate", $"{checkInDate:yyyy'-'MM'-'dd}")
                            .AddParameter("checkOutDate", $"{checkOutDate:yyyy'-'MM'-'dd}")
                            .Uri;
            return _httpClient.GetFromJsonAsync<IEnumerable<HotelRoomDTO>>(uri);
        }
    }
}
توضیحات:
- HttpClient یکی از سرویس‌های تنظیم شده‌ی در فایل Program.cs پروژه‌های سمت کلاینت است. بنابراین می‌توان آن‌را از طریق تزریق به سازنده‌ی این سرویس، به دست آورد.
- در اینجا برای دریافت اطلاعات JSON دریافتی از سمت سرور و سپس Deserialize خودکار آن به لیستی از DTO تعریف شده، از متد جدید GetFromJsonAsync استفاده شده‌است. این مورد جزو تازه‌های NET 5x. است.
- در اینجا استفاده از کلاس UriBuilderExt را نیز جهت تشکیل یک URL دارای کوئری استرینگ، مشاهده می‌کنید. هیچگاه نباید URL نهایی را از طریق جمع زدن اجزای آن به سمت سرور ارسال کرد؛ از این جهت که اجزای آن باید URL-encoded شوند؛ وگرنه در سمت سرور قابلیت پردازش نخواهند داشت. در ادامه تعریف کلاس جدید UriBuilderExt را مشاهده می‌کنید:
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using BlazorServer.Models;
using BlazorWasm.Client.Utils;

using System;
using System.Collections.Specialized;
using System.Web;

namespace BlazorWasm.Client.Utils
{
    public class UriBuilderExt
    {
        private readonly NameValueCollection _collection;
        private readonly UriBuilder _builder;

        public UriBuilderExt(Uri uri)
        {
            _builder = new UriBuilder(uri);
            _collection = HttpUtility.ParseQueryString(string.Empty);
        }

        public UriBuilderExt AddParameter(string key, string value)
        {
            _collection.Add(key, value);
            return this;
        }

        public Uri Uri
        {
            get
            {
                _builder.Query = _collection.ToString();
                return _builder.Uri;
            }
        }
    }
}
- در اینجا توسط متد AddParameter، کار افزودن کوئری استرینگ‌ها به یک Url از پیش مشخص، انجام می‌شود. کار encoding نهایی به صورت خودکار توسط HttpUtility استاندارد دات نت، انجام خواهد شد.
- تاریخ‌های ارسالی به سمت سرور را با فرمت yyyy'-'MM'-'dd تبدیل رشته کردیم. این قالب، یکی از قالب‌های پذیرفته شده‌است.
- جهت سهولت استفاده‌ی از سرویس فوق و همچنین مدل‌های برنامه، فضای نام آن‌ها را به فایل BlazorWasm.Client\_Imports.razor اضافه می‌کنیم تا در تمام کامپوننت‌های برنامه‌ی سمت کلاینت، قابل دسترسی شوند:
@using BlazorWasm.Client.Services
@using BlazorServer.Models
- در آخر این سرویس جدید را باید به لیست سرویس‌های برنامه معرفی کرد تا قابلیت تزریق در کامپوننت‌ها را پیدا کند:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ...

            builder.Services.AddScoped<IClientHotelRoomService, ClientHotelRoomService>();

            // ...
        }
    }
}

چند اصلاح جزئی در کنترلرها و سرویس‌های سمت سرور

در Url نهایی فوق، دو پارامتر جدید checkInDate و checkOutDate هم وجود دارند. به همین جهت این دو را به اکشن متدهای کنترلر HotelRoom:
namespace BlazorWasm.WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HotelRoomController : ControllerBase
    {
        // ...

        [HttpGet]
        public async Task<IActionResult> GetHotelRooms(DateTime? checkInDate, DateTime? checkOutDate)
        {
          // ...
        }

        [HttpGet("{roomId}")]
        public async Task<IActionResult> GetHotelRoom(int? roomId, DateTime? checkInDate, DateTime? checkOutDate)
        {
           // ...
        }
    }
}
و همچنین سرویس سمت سرور IHotelRoomService نیز اضافه می‌کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService : IDisposable
    {
        Task<List<HotelRoomDTO>> GetAllHotelRoomsAsync(DateTime? checkInDate, DateTime? checkOutDate);
        Task<HotelRoomDTO> GetHotelRoomAsync(int roomId, DateTime? checkInDate, DateTime? checkOutDate);
        // ...
    }
}
البته فعلا پیاده سازی خاصی ندارند و آن‌ها را در قسمت‌های بعد مورد استفاده قرار خواهیم داد.


تنظیمات ویژه‌ی HttpClient برنامه‌ی سمت کلاینت

سرویس ClientHotelRoomService فوق، از HttpClient تزریق شده‌ی در سازنده‌ی خود استفاده می‌کند که BaseAddress خود را مطابق تنظیمات ابتدایی برنامه، از HostEnvironment دریافت می‌کند. در اینجا علاقمندیم تا بجای این تنظیم پیش‌فرض، فایل جدید appsettings.json را به پوشه‌ی BlazorWasm.Client\wwwroot\appsettings.json کلاینت اضافه کرده (محل قرارگیری آن در برنامه‌های سمت کلاینت، داخل پوشه‌ی wwwroot است و نه در داخل پوشه‌ی ریشه‌ی اصلی پروژه):
{
    "BaseAPIUrl": "https://localhost:5001/"
}
و از این تنظیم جدید به عنوان BaseAddress برنامه‌ی Web API استفاده کنیم که روش آن‌را در کدهای ذیل مشاهده می‌کنید:
namespace BlazorWasm.Client
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            // ... 

            // builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
            builder.Services.AddScoped(sp => new HttpClient
            {
                BaseAddress = new Uri(builder.Configuration.GetValue<string>("BaseAPIUrl"))
            });

            // ... 
        }
    }
}

تکمیل کامپوننت دریافت لیست تمام اتاق‌ها

در قسمت قبل، کامپوننت خالی HotelRooms.razor را تعریف کردیم. کاربران پس از کلیک بر روی دکمه‌ی Go صفحه‌ی اول، به این کامپوننت هدایت می‌شوند. اکنون می‌خواهیم، لیست تمام اتاق‌ها را در این کامپوننت، از Web API برنامه دریافت کنیم:
@page "/hotel/rooms"

@inject ILocalStorageService LocalStorage
@inject IJSRuntime JsRuntime
@inject IClientHotelRoomService HotelRoomService

<h3>HotelRooms</h3>

@code {
    HomeVM HomeModel = new HomeVM();
    IEnumerable<HotelRoomDTO> Rooms = new List<HotelRoomDTO>();

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var model = await LocalStorage.GetItemAsync<HomeVM>(ConstantKeys.LocalInitialBooking);
            if (model is not null)
            {
                HomeModel = model;
            }
            else
            {
                HomeModel.NoOfNights = 1;
            }
            await LoadRooms();
        }
        catch (Exception e)
        {
            await JsRuntime.ToastrError(e.Message);
        }
    }

    private async Task LoadRooms()
    {
        Rooms = await HotelRoomService.GetHotelRoomsAsync(HomeModel.StartDate, HomeModel.EndDate);
    }
}
در اینجا در ابتدا سعی می‌شود تا HomeModel، از Local Storage که در قسمت قبل آن‌را تنظیم کردیم، خوانده شود. سپس با استفاده از متد GetHotelRoomsAsync، لیست اتاق‌ها را از Web API دریافت می‌کنیم. تمام این عملیات آغازین نیز باید در روال رویدادگران OnInitializedAsync صورت گیرند.


روش اجرای پروژه‌های Blazor WASM

تا اینجا اگر برنامه‌ی سمت کلاینت را توسط دستور dotnet watch run اجرا کنیم، هرچند صفحه‌ی خالی نمایش لیست اتاق‌ها ظاهر می‌شود، اما یک خطای fetch error را هم دریافت خواهیم کرد؛ چون نیاز است ابتدا پروژه‌ی Web API را اجرا کرد و سپس پروژه‌ی WASM را.
برای ساده سازی اجرای همزمان این دو پروژه، اگر از ویژوال استودیوی کامل استفاده می‌کنید، بر روی نام Solution کلیک راست کرده و از منوی ظاهر شده، گزینه‌ی «Set Startup projects» را انتخاب کنید. در صفحه دیالوگ ظاهر شده، گزینه‌ی «multiple startup projects» را انتخاب کرده و از لیست پروژه‌های موجود، دو پروژه‌ی Web API و WASM را انتخاب کنید و Action مقابل آن‌ها را به Start تنظیم کنید. در اینجا حتی می‌توان ترتیب اجرای این پروژه‌ها را هم تغییر داد. در این حالت زمانیکه بر روی دکمه‌ی Run، در ویژوال استودیو کلیک می‌کنید، هر دو پروژه را با هم برای شما اجرا خواهد کرد.

نکته‌ی مهم! در این حالت هم برنامه‌ی کلاینت نمی‌تواند با برنامه‌ی Web API ارتباط برقرار کند! چون شماره پورت iisExpress درج شده‌ی در فایل appsettings.json آن، باید به شماره sslPort مندرج در فایل Properties\launchSettings.json پروژه‌ی Web API تغییر داده شود که برای نمونه در اینجا این عدد 44314 است:
{
  "iisSettings": {
    "iisExpress": {
      "applicationUrl": "http://localhost:62930",
      "sslPort": 44314
    }
  }
}
و یا اگر می‌خواهید پروژه را از طریق NET Core CLI. با اجرای دستور dotnet watch run اجرا کنید ... به صورت پیش‌فرض نمی‌شود! چون برای اینکار باید به پوشه‌ی ریشه‌ی پروژه‌های Web API و WASM وارد شد و دوبار دستور یاد شده را به صورت مجزا اجرا کرد. در این حالت، هر دو پروژه، بر روی پورت پیش‌فرض 5001 اجرا می‌شوند. روش تغییر این پورت، مراجعه به فایل Properties\launchSettings.json این پروژه‌ها است. برای مثال همان پورت پیش‌فرض 5001 را که در فایل appsettings.json انتخاب کردیم، ثابت نگه می‌داریم. یعنی فایل launchSettings.json پروژه‌ی Web API را ویرایش نمی‌کنیم. اما این پورت را در پروژه‌ی کلاینت برای مثال به عدد 5002 تغییر می‌دهیم تا برنامه‌ی کلاینت، بر روی پورت پیش‌فرض برنامه‌ی Web API اجرا نشود:
{
    "BlazorWasm.Client": {
      "applicationUrl": "https://localhost:5002;http://localhost:5003",
    }  
}


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-27.zip
مطالب
خلاصه اشتراک‌های روز یک شنبه 1390/06/27

نظرات مطالب
تغییر استراتژی ساخت مدل در EF5 و رفع مشکل WCF Ria
میشه بگید توی یک برنامه asp.net ساده که توی vs2012 نوشته شده راه حل چیه؟ چون در صورتی که Code Generation Strategy  رو تغییر ندیم دیگه به مدل دسترسی نداریم؟ (روش database first) 
نظرات مطالب
NoSQL ؟
- در ravendb امکان replication به sql server وجود دارد.
- یکی از اهداف مهم ORMها در دات نت، نوشتن کوئری‌های strongly typed است. در ravendb شما از روز اول با کوئری‌های strongly typed سروکار دارید. همچنین از همان ابتدای کار هم با کلاس‌های دات نتی و نگاشت خودکار آن‌ها کار می‌کنید. کلا ravendb برمبنای معماری و همچنین توانمندی و پیشرفت‌های زبان‌های دات نتی تهیه شده.