مطالب
بررسی تغییرات Blazor 8x - قسمت چهاردهم - امکان استفاده از کامپوننت‌های Blazor در برنامه‌های ASP.NET Core 8x
ASP.NET Core 8x به همراه یک IResult جدید به‌نام RazorComponentResult است که توسط آن می‌توان در Endpoint‌های Minimal-API و همچنین اکشن متدهای MVC، از کامپوننت‌های Blazor، خروجی گرفت. این خروجی نه فقط static یا به عبارتی SSR، بلکه حتی می‌تواند تعاملی هم باشد. در این مطلب، جزئیات فعالسازی و استفاده از این IResult جدید را در یک برنامه‌ی Minimal-API بررسی می‌کنیم.


ایجاد یک برنامه‌ی Minimal-API جدید در دات نت 8

پروژه‌ای را که در اینجا پیگیری می‌کنیم، بر اساس قالب استاندارد تولید شده‌ی توسط دستور dotnet new webapi تکمیل می‌شود.


ایجاد یک صفحه‌ی Blazor 8x به همراه مسیریابی و دریافت پارامتر

در ادامه قصد داریم که یک کامپوننت جدید را به نام SsrTest.razor در پوشه‌ی جدید Components\Tests ایجاد کرده و برای آن مسیریابی از نوع page@ هم تعریف کنیم. یعنی نه‌فقط قصد داریم آن‌را توسط RazorComponentResult رندر کنیم، بلکه می‌خواهیم اگر آدرس آن‌را در مرورگر هم وارد کردیم، قابل دسترسی باشد.
به همین جهت یک پوشه‌ی جدید را به نام Components در ریشه‌ی پروژه‌ی Web API جاری ایجاد می‌کنیم، با این محتوا:
برای ایده گرفتن از محتوای مورد نیاز، به «معرفی قالب‌های جدید شروع پروژه‌های Blazor در دات نت 8» قسمت دوم این سری مراجعه کرده و برای مثال قالب ساده‌ترین حالت ممکن را توسط دستور زیر تولید می‌کنیم (در یک پروژه‌ی مجزا، خارج از پروژه‌ی جاری):
dotnet new blazor --interactivity None
پس از اینکار، محتویات پوشه‌ی Components آن‌را مستقیما داخل پوشه‌ی پروژه‌ی Minimal-API جاری کپی می‌کنیم. یعنی در نهایت در این پروژه‌ی جدید Web API، به فایل‌های زیر می‌رسیم:
- فایل Imports.razor_ ساده شده برای سهولت کار با فضاهای نام در کامپوننت‌های Blazor (فضاهای نامی را که در آن وجود ندارند و مرتبط با پروژه‌ی دوم هستند، حذف می‌کنیم).
- فایل App.razor، برای تشکیل نقطه‌ی آغازین برنامه‌ی Blazor.
- فایل Routes.razor برای معرفی مسیریابی صفحات Blazor تعریف شده.
- پوشه‌ی Layout برای معرفی فایل MainLayout.razor که در Routes.razor استفاده شده‌است.

و ... یک فایل آزمایشی جدید به نام Components\Tests\SsrTest.razor با محتوای زیر:
@page "/ssr-page/{Data:int}"

<PageTitle>An SSR component</PageTitle>

<h1>An SSR component rendered by a Minimal-API!</h1>

<div>
    Data: @Data
</div>

@code {

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

}
این فایل، می‌تواند پارامتر Data را از طریق فراخوانی مستقیم آدرس فرضی http://localhost:5227/ssr-page/2 دریافت کند و یا ... از طریق خروجی جدید RazorComponentResult که توسط یک Endpoint سفارشی ارائه می‌شود:




تغییرات مورد نیاز در فایل Program.cs برنامه‌ی Web-API برای فعالسازی رندر سمت سرور Blazor

در ادامه کل تغییرات مورد نیاز جهت اجرای این برنامه را مشاهده می‌کنید:
var builder = WebApplication.CreateBuilder(args);

// ...

builder.Services.AddRazorComponents();

// ...

// http://localhost:5227/ssr-component?data=2
// or it can be called directly http://localhost:5227/ssr-page/2
app.MapGet("/ssr-component",
           (int data = 1) =>
           {
               var parameters = new Dictionary<string, object?>
                                {
                                    { nameof(SsrTest.Data), data },
                                };
               return new RazorComponentResult<SsrTest>(parameters);
           });

app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>();
app.Run();

// ...
توضیحات:
- همین اندازه تغییر در جهت فعالسازی رندر سمت سرور کامپوننت‌های Blazor در یک برنامه‌ی ASP.NET Core کفایت می‌کند. یعنی اضافه شدن:
AddRazorComponents ،UseAntiforgery و MapRazorComponents
- در اینجا نحوه‌ی ارسال پارامترها را به یک RazorComponentResult نیز مشاهده می‌کنید.
- در حالت فراخوانی از طریق مسیر endpoint (یعنی فراخوانی مسیر http://localhost:5227/ssr-component در مثال فوق)، خود کامپوننت فراخوانی شده، بدون layout تعریف شده‌ی در فایل App.razor، رندر می‌شود. علت اینجا است که layout برنامه به همراه کامپوننت Router و RouteView آن فعال می‌شود که این دو هم مختص به صفحات دارای مسیریابی Blazor هستند و برای رندر کامپوننت‌های خالص آن بکار گرفته نمی‌شوند. خروجی RazorComponentResult تنها یک static SSR خالص است؛ مگر اینکه فایل blazor.web.js را نیز بارگذاری کند.

یک نکته: اگر در حالت رندر توسط RazorComponentResult، علاقمند به استفاده‌ی از layout هستید، می‌توان از کامپوننت LayoutView داخل یک کامپوننت فرضی به صورت زیر استفاده کرد؛ اما این مورد هم شامل اطلاعات فایل App.razor نمی‌شود:
<LayoutView Layout="@typeof(MainLayout)">
    <PageTitle>Home</PageTitle>

    <h2>Welcome to your new app.</h2>
</LayoutView>


سؤال: آیا در این حالت کامپوننت‌های تعاملی هم کار می‌کنند؟

پاسخ: بله. فقط برای ایده گرفتن، یک نمونه پروژه‌ی تعاملی Blazor 8x را در ابتدا ایجاد کنید و قسمت‌های اضافی AddRazorComponents و MapRazorComponents آن‌را در اینجا کپی کنید؛ یعنی برای مثال جهت فعالسازی کامپوننت‌های تعاملی Blazor Server، به این دو تغییر زیر نیاز است:
// ...

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

// ...

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

// ...
همچنین باید دقت داشت که امکانات تعاملی، به دلیل وجود و دسترسی به یک سطر ذیل که در فایل Components\App.razor واقع شده، اجرایی می‌شوند:
<script src="_framework/blazor.web.js"></script>
و همانطور که عنوان شد، اگر از روش new RazorComponentResult استفاده می‌شود، باید این سطر را به صورت دستی اضافه‌کرد؛ چون به همراه رندر layout تعریف شده‌ی در فایل App.razor نیست. برای مثال فرض کنید کامپوننت معروف Counter را به صورت زیر داریم که حالت رندر آن به InteractiveServer تنظیم شده‌است:
@rendermode InteractiveServer

<h1>Counter</h1>

<p role="status">Current count: @_currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int _currentCount;

    private void IncrementCount()
    {
        _currentCount++;
    }

}
در این حالت پس از تعریف endpoint زیر، خروجی آن فقط یک صفحه‌ی استاتیک SSR خواهد بود و دکمه‌ی Click me آن کار نمی‌کند:
// http://localhost:5227/server-interactive-component
app.MapGet("/server-interactive-component", () => new RazorComponentResult<Counter>());
علت اینجا است که اگر به سورس HTML رندر شده مراجعه کنیم، خبری از درج اسکریپت blazor.web.js در انتهای آن نیست. به همین جهت برای مثال فایل جدید CounterInteractive.razor را به صورت زیر اضافه می‌‌کنیم که ساختار آن شبیه به فایل App.razor است:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive server component</title>
    <base href="/"/>
</head>
<body>
   <h1>Interactive server component</h1>

   <Counter/>

  <script src="_framework/blazor.web.js"></script>
</body>
</html>
و هدف اصلی از آن، تشکیل یک قالب و درج اسکریپت blazor.web.js در انتهای آن است.
سپس تعریف endpoint متناظر را به صورت زیر تغییر می‌دهیم:
// http://localhost:5227/server-interactive-component
app.MapGet("/server-interactive-component", () => new RazorComponentResult<CounterInteractive>());
اینبار به علت بارگذاری فایل blazor.web.js، امکانات تعاملی کامپوننت Counter فعال شده و قابل استفاده می‌شوند.


سؤال: آیا می‌توان این خروجی static SSR کامپوننت‌های بلیزر را در سرویس‌های یک برنامه ASP.NET Core هم دریافت کرد؟

منظور این است که آیا می‌توان از یک کامپوننت Blazor، به همراه تمام پیشرفت‌های Razor در آن که در Viewهای MVC قابل دسترسی نیستند، به‌شکل یک رشته‌ی خالص، خروجی گرفت و برای مثال از آن به‌عنوان قالب پویای محتوای ایمیل‌ها استفاده کرد؟
پاسخ: بله! زیر ساخت RazorComponentResult که از سرویس HtmlRenderer استفاده می‌کند، بدون نیاز به برپایی یک endpoint هم قابل دسترسی است:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace WebApi8x.Services;

public class BlazorStaticRendererService
{
    private readonly HtmlRenderer _htmlRenderer;

    public BlazorStaticRendererService(HtmlRenderer htmlRenderer) => _htmlRenderer = htmlRenderer;

    public Task<string> StaticRenderComponentAsync<T>() where T : IComponent
        => RenderComponentAsync<T>(ParameterView.Empty);

    public Task<string> StaticRenderComponentAsync<T>(Dictionary<string, object?> dictionary) where T : IComponent
        => RenderComponentAsync<T>(ParameterView.FromDictionary(dictionary));

    private Task<string> RenderComponentAsync<T>(ParameterView parameters) where T : IComponent =>
        _htmlRenderer.Dispatcher.InvokeAsync(async () =>
                                             {
                                                 var output = await _htmlRenderer.RenderComponentAsync<T>(parameters);
                                                 return output.ToHtmlString();
                                             });
}
برای کار با آن، ابتدا باید سرویس فوق را به صورت زیر ثبت و معرفی کرد:
builder.Services.AddScoped<HtmlRenderer>();
builder.Services.AddScoped<BlazorStaticRendererService>();
و سپس یک نمونه مثال فرضی نحوه‌ی تزریق و فراخوانی سرویس BlazorStaticRendererService به صورت زیر است که در آن روش ارسال پارامترها هم بررسی شده‌است:
app.MapGet("/static-renderer-service-test",
           async (BlazorStaticRendererService renderer, int data = 1) =>
           {
               var parameters = new Dictionary<string, object?>
                                {
                                    { nameof(SsrTest.Data), data },
                                };
               var html = await renderer.StaticRenderComponentAsync<SsrTest>(parameters);
               return Results.Content(html, "text/html");
           });

کدهای کامل این مطلب را می‌توانید از اینجا دریافت کنید: WebApi8x.zip
مطالب
کامپوننت‌های جنریک در Blazor
طرح مشکل! نیاز به دریافت انواع و اقسام مقادیر یک جنس (مانند اعداد و یا تاریخ) در کامپوننت‌های Blazor

فرض کنید می‌خواهید عددی را در کامپوننتی، دریافت کنید. می‌توان اینکار را با تعریف یک پارامتر عمومی به صورت زیر انجام داد:
[Parameter] public int Value { get; set; }
و ... مشکل از همینجا شروع می‌شود! خوب، برای نوع‌های double ، decimal ، float ، long و غیره چه باید کرد؟ آیا باید به ازای هر کدام، یک پارامتر مخصوص را تعریف کنیم؟ و یا اگر خواستیم زمان و تاریخ را از کاربر دریافت کنیم، آیا باید او را مجبور به ارائه‌ی فقط DateTime کنیم و یا ... شاید بهتر باشد به او بگوییم اگر «رشته‌ای» را در اختیار ما قرار دهید، ما یکبار دیگر خودمان کار تبدیل آن‌را به نوع تاریخ انجام خواهیم داد!
برای حل این نوع مشکلات، می‌توان در Blazor پارامترها را جنریک تعریف کرد. برای نمونه اگر به طراحی یک سری کامپوننت‌های پایه‌ای Blazor دقت کنیم، امکان دریافت مستقیم انواع و اقسام اعداد و یا انواع و اقسام نوع‌های مرتبط با زمان را دارند؛ بدون اینکه این اطلاعات را به صورت رشته‌ای دریافت کنند و یا به ازای هر نوع عددی، یک پارامتر جداگانه را تعریف و یا اینکه استفاده کننده را محدود به ورود تنها یک نوع عددی و یا زمانی خاص کرده باشند.


نحوه‌ی جنریک تعریف کردن یک کامپوننت در Blazor

Blazor فایل‌های razor. را در حین پروسه‌ی build، به cs. تبدیل می‌کند و از آنجائیکه فایل‌های razor. الزامی به تعریف نام کلاس مرتبط را ندارند و این نام به صورت خودکار دقیقا از نام خود فایل، دریافت می‌شود، نیاز است راهی را یافت تا بتوان کلاس مرتبط را به صورت Generic تعریف کرد. برای این منظور، دایرکتیو ویژه‌ای به نام typeparam@ پیش‌ بینی شده‌است که توسط آن می‌توان یک یا چند نوع/پارامتر جنریک جدا شده‌ی توسط کاما را تعریف کنیم (تعریف شده‌ی در فایل فرضی MyGenericComponent.razor):
@typeparam T

@code {
    [Parameter] public T Value { get; set; }
}
در این مثال کامپوننتی را مشاهده می‌کنید که مقدار دریافتی خود را به صورت جنریک از نوع T دریافت می‌کند. این T باید توسط دایرکتیو typeparam@ دقیقا قید شود تا در حین ساخت خودکار کلاس معادل این فایل، مورد استفاده قرار گیرد.


نحوه‌ی جنریک تعریف کردن کامپوننت‌های دارای code-behind

اگر قسمت code@ فایل‌های razor. را به فایل‌های code-benind منتقل می‌کنید و داخل فایل razor.، کد #C نمی‌نویسید، روش جنریک تعریف کردن یک کامپوننت، به صورت زیر خواهد بود (برای مثال دو فایل MyGenericComponentWithCodeBehind.razor و MyGenericComponentWithCodeBehind.razor.cs تعریف شده‌اند):
using Microsoft.AspNetCore.Components;

namespace BlazorGenericComponents.Pages;

public partial class MyGenericComponentWithCodeBehind<T>
{
    [Parameter] public T Value { get; set; }
}
در اینجا ذکر دایرکتیو typeparam@ در فایل razor. متناظر هم باید صورت گیرد:
@typeparam T
یعنی هر دو کلاس نهایی حاصل از این فایل‌ها که به صورت partial تعریف شده‌اند (و در آخر یکی می‌شوند)، باید به صورت جنریک تعریف شوند.


روش مقید کردن پارامترهای جنریک در کامپوننت‌ها

از زمان دات نت 6 به بعد، امکان محدود کردن بازه‌ی نوع‌های قابل پذیرش T نیز میسر شده‌است:
@typeparam T where T : IMyInterface


روش مشخص کردن صریح نوع پارامترهای جنریک، در حین استفاده‌ی از آن‌ها

عموما نیازی به مشخص کردن نوع پارامترهای جنریک، در حین استفاده‌ی از آن‌ها نیست و کامپایلر بر اساس نوع مقدار ورودی، سعی خواهد کرد این نوع را به صورت خودکار تشخیص دهد؛ اما اگر به هر دلیلی چنین امری ممکن نبود و خطایی را دریافت کردید، می‌توان نوع پارامتر جنریک را به صورت زیر مشخص کرد:
<MyGenericComponent Value="10" T="int"/>
در اینجا نام پارامتر جنریک، ذکر شده و سپس نوعی به آن انتساب داده می‌شود.


روش پردازش پارامترهای جنریک در کامپوننت‌ها

تا اینجا امکان پذیرش نوع‌های جنریک را توسط استفاده کننده میسر کردیم؛ اما قسمت دیگر آن، نحوه‌ی کار با نوع T، درون یک کامپوننت است:
using Microsoft.AspNetCore.Components;

namespace BlazorGenericComponents.Pages;

public partial class MyGenericComponentWithCodeBehind<T>
{
    private string FormattedValue { set; get; }

    [Parameter] public T Value { get; set; }

    [Parameter] public string Format { get; set; }

    protected override void OnParametersSet()
    {
        FormattedValue = ConvertNumberToText();
    }

    private string ConvertNumberToText()
    {
        return Value switch
        {
            short shortValue => shortValue.ToString(Format),
            ushort ushortValue => ushortValue.ToString(Format),
            int intValue => intValue.ToString(Format),
            uint uintValue => uintValue.ToString(Format),
            byte byteValue => byteValue.ToString(Format),
            sbyte sbyteValue => sbyteValue.ToString(Format),
            decimal decimalValue => decimalValue.ToString(Format),
            double doubleValue => doubleValue.ToString(Format),
            float floatValue => floatValue.ToString(Format),
            long longValue => longValue.ToString(Format),
            ulong ulongValue => ulongValue.ToString(Format),
            _ => string.Empty
        };
    }
}
برای مثال فرض کنید کامپوننت جنریک ما قرار است انواع و اقسام اعداد را بپذیرد و سپس بر اساس Format مشخص شده، آن‌ها را نمایش دهد. در اینجا جهت واکنش نشان دادن به تغییرات Value، می‌توان از روال رخ‌داد گردان OnParametersSet استفاده کرد. سپس در متد ConvertNumberToText، بر اساس هر نوع میسر عددی، باید منطق ویژه‌ای را تهیه کرد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید.

و در آخر نمایش نهایی آن هم در فایل razor. متناظر، به این صورت خواهد بود:
@typeparam T

@FormattedValue
اشتراک‌ها
دوره سه ساعته آموزش Blazor

00:00:00 Introduction
00:03:07 Blazor Project Structure
00:12:14 How Blazor Works
00:20:28 What is an Inventory Management System
00:23:47 Introduction to Clean Architecture
00:30:58 Null Reference Type in .NET 6
00:36:44 Write the View Inventories Use Case
00:45:16 Implement the View Inventories Use Case
00:59:27 Create a Plugin with Dependency Injection
01:08:45 Inject the Use Case in Razor Component
01:17:35 Dependency Injection in Blazor
01:28:08 Page Component - Create the Inventory List Page
01:34:34 SPA Components Best Practice
01:38:07 Databinding and EventCallback in Search Inventory Component
01:52:59 Component Parameters in Inventory List Component
02:03:52 Null Checks
02:06:07 Extract the Inventory List Item Component
02:09:45 Add Inventory Use Case
02:12:10 Implement Add Inventory Repository Methods
02:15:34 NavigationManager
02:17:56 EditForm and Data Validation
02:30:09 Edit Inventory Use Case
02:35:12 Implement Edit Inventory Repository methods
02:43:00 Receive Routing Parameters
02:50:09 Implement Edit Inventory Component
03:03:06 Why we are using Async everywhere 

دوره سه ساعته آموزش Blazor
اشتراک‌ها
نحوه ساخت اپلیکیشنهای Real-time تحت وب با ASP.NET Core SignalR

 show how easy it is to add real-time functionality to your web applications using ASP.NET Core SignalR. They discuss topics such as targeting with clients, SignalR transports, and options for running your SignalR application in the cloud.

Now, you can even leverage the Hub protocol spec is the available on GitHub if you're interested in creating your own SignalR client.

  • [02:10]  - Quick introduction to SignalR
  • [06:06] - Targeting clients
  • [10:51] - Leveraging the Hub protocol
  • [12:18]  - SignalR Transports
  • [13:55] - Hosting SignalR in a background service
  • [16:22] - Azure SignalR Service
  • [18:22] - The SignalR Java client on Android
  • [22:34] - Streaming with SignalR 
نحوه ساخت اپلیکیشنهای Real-time تحت وب با ASP.NET Core SignalR
نظرات اشتراک‌ها
بررسی آینده‌ی Blazor در دات نت 8
خلاصه‌ی این مفهوم «United» به این صورت است:
- blazor server از لحاظ عدم نیاز به دریافت چند صد فایل مرتبط با web assembly و همراه بودن با رندر سمت سرور، در ابتدای کار نمایش برنامه، سریعتر نمایش داده می‌شود و این نمایش اولیه تقریبا آنی است.
- Blazor WASM چون به طور کامل در مرورگر اجرا می‌شود و در جهت رندر صفحات نیازی به رفت و برگشت به سرور ندارد، سرعت اجرایی فوق العاده‌ای دارد؛ اما به همراه بارگذاری تعداد زیادی فایل در ابتدای کار نمایش آن است که ... کمی طول می‌کشد که البته می‌توان با استفاده از فعال سازی per-rendering کمی آن‌را سریعتر کرد که نیاز به تنظیمات قابل توجهی دارد که شاید همه از آن استفاده نکنند.
اکنون قرار است در دات نت 8 بتوان به صورت خودکار ابتدا یک صفحه را با استفاده از server side rendering موجود در Blazor Server، سریع نمایش داد (بدون نیاز به دریافت فایل‌های WASM و منتظر شدن برای آن‌ها) و بعد در همان حال در پشت صحنه و به صورت خودکار، همین برنامه با قالب Blazor web assembly هم دریافت می‌شود تا در بار بعدی که این صفحه قرار است نمایش داده شود، بتوان به حداکثر سرعت کار با Blazor، با استفاده از اجرای کامل آن در مرورگر توسط web assembly رسید. یعنی ترکیب همزمان blazor server و blazor web assembly، بدون نیاز به تنظیمات خاصی در جهت از پیش رندر کردن صفحات و یا منتظر ماندن اولیه و نمایش یک صفحه‌ی خالی در جهت بارگذاری تمام فایل‌های یک برنامه‌ی web assembly؛ هر دو با هم به صورت یکپارچه و یک‌دست. حتی می‌توان مشخص کرد که صفحه‌ای فقط بر اساس blazor server اجرا شود و صفحه‌ای دیگر فقط قسمتی از آن از blazor wasm استفاده کند.
مطالب
امکان استفاده از کتابخانه‌های native در Blazor WASM 6x
کتابخانه‌‌های بسیاری هستند که به زبان‌های C ، C++ ، Rust و امثال آن تهیه شده‌اند. دات نت 6، قابلیت جدید استفاده‌ی از این نوع کتابخانه‌ها را بدون نیاز به تبدیل کدهای آن‌ها به #C، به برنامه‌های سمت کلاینت Blazor Web Assembly اضافه کرده که در این مطلب، نمونه‌ای از آن‌را با استفاده از بانک اطلاعاتی SQLite در برنامه‌های Blazor WASM 6x، بررسی خواهیم کرد. یعنی یک برنامه‌ی SPA سمت کلاینت که بدون نیاز به سرور و Web API، تنها با استفاده از EF-Core و بانک اطلاعاتی بومی SQLite می‌تواند اطلاعات مورد نیاز خود را ثبت و یا بازیابی کند (همه چیز داخل مرورگر رخ می‌دهد).


ایجاد یک پروژه‌ی Blazor WASM جدید

یک پوشه‌ی جدید دلخواه را به نام BlazorWasmSQLite ایجاد کرده و با اجرای دستور dotnet new blazorwasm، یک پروژه‌ی Blazor Web Assembly خالی جدید را در آن آغاز می‌کنیم. همانطور که از دستور نیز مشخص است، این پروژه از نوع hosted که به همراه Web API هم هست، نمی‌باشد.


افزودن Context و مدل EF-Core به برنامه

مدل برنامه به صورت زیر در پوشه‌ی Models آن قرار می‌گیرد:
namespace BlazorWasmSQLite.Models;

public class Car
{
  public int Id { get; set; }

  public string Brand { get; set; }

  public  int Price { get; set; }
}
و Context ای که آن‌را در معرض دید قرار می‌دهد، به صورت زیر تعریف خواهد شد:
using Microsoft.EntityFrameworkCore;
using BlazorWasmSQLite.Models;

namespace BlazorWasmSQLite.Data;

public class ClientSideDbContext : DbContext
{
  public DbSet<Car> Cars { get; set; } = default!;

  public ClientSideDbContext(DbContextOptions<ClientSideDbContext> options) :
    base(options)
  {
  }
}
همچنین چون می‌خواهیم از بانک اطلاعاتی SQLite استفاده کنیم، وابستگی زیر را به فایل csproj برنامه اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
  <ItemGroup>
    <!-- EF Core and Sqlite -->
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
  </ItemGroup>
</Project>
سپس این Context را به نحو زیر به فایل Program.cs معرفی می‌کنیم:
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using BlazorWasmSQLite;
using Microsoft.EntityFrameworkCore;
using BlazorWasmSQLite.Data;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

// Sets up EF Core with Sqlite
builder.Services.AddDbContextFactory<ClientSideDbContext>(options =>
      options
        .UseSqlite($"Filename=DemoData.db")
        .EnableSensitiveDataLogging());

await builder.Build().RunAsync();
در مورد علت استفاده‌ی از AddDbContextFactory و نکات مرتبط با آن، به مطلب «نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server» مراجعه نمائید.


ثبت تعدادی رکورد در بانک اطلاعاتی

در ادامه سعی می‌کنیم در فایل Index.razor، تعدادی رکورد را به بانک اطلاعاتی اضافه کنیم:
@page "/"

@using Microsoft.Data.Sqlite
@using Microsoft.EntityFrameworkCore
@using BlazorWasmSQLite.Data
@using BlazorWasmSQLite.Models


<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

@code {
  [Inject]
  private IDbContextFactory<ClientSideDbContext> _dbContextFactory { get; set; } = default!;

  protected override async Task OnInitializedAsync()
  {
    await using var db = await _dbContextFactory.CreateDbContextAsync();
    await db.Database.EnsureCreatedAsync();

    // create seed data
    if (!db.Cars.Any())
    {
      var cars = new[]
      {
        new Car { Brand = "Audi", Price = 21000 },
        new Car { Brand = "Volvo", Price = 11000 },
        new Car { Brand = "Range Rover", Price = 135000 },
        new Car { Brand = "Ford", Price = 8995 }
      };

      await db.Cars.AddRangeAsync(cars);
      await db.SaveChangesAsync();
    }

    await base.OnInitializedAsync();
  }
}
در این مثال سعی شده‌است ساده‌ترین حالت ممکن کار با EF-Core در پیش گرفته شود؛ چون هدف اصلی آن، دسترسی به SQLite است.


اولین سعی در اجرای برنامه

در ادامه سعی می‌کنیم تا برنامه را اجرا کنیم. با خطای زیر متوقف خواهیم شد:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] Unhandled exception rendering component:
The type initializer for 'Microsoft.Data.Sqlite.SqliteConnection' threw an exception. System.TypeInitializationException:
The type initializer for 'Microsoft.Data.Sqlite.SqliteConnection' threw an exception. ---> System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. ---> System.DllNotFoundException: e_sqlite3 at
SQLitePCL.SQLite3Provider_e_sqlite3.SQLitePCL.ISQLite3Provider.sqlite3_libversion_number()
عنوان می‌کند که فایل‌های بانک اطلاعاتی SQLite به همراه EF-Core را نمی‌تواند پیدا کند. یا به عبارتی هر DLL بومی را نمی‌توان داخل مرورگر اجرا کرد.


رفع مشکل کار با SQLite با کامپایل ویژه‌ی آن

برای دسترسی به کدهای native در Blazor WASM و مرورگر، باید آن‌ها را توسط کامپایلر emcc به صورت زیر کامپایل کرد:
$ git clone https://github.com/cloudmeter/sqlite
$ cd sqlite
$ emcc sqlite3.c -shared -o e_sqlite3.o
در اینجا هر نوع فایل portable native code با فرمت‌های o. یا object files، .a و یا archive files و یا .bc یا bitcode و یا .wasm یا Standalone WebAssembly modules توسط Blazor wasm قابل استفاده هستند که در مثال فوق نمونه‌ی object files آن‌ها توسط کامپایلر تولید می‌شود.
مرحله‌ی بعد، معرفی این object file تولید شده به برنامه است. برای اینکار ابتدا باید dotnet workload install wasm-tools را نصب کرد (مهم). سپس به فایل csproj برنامه مراجعه کرده و فایل e_sqlite3.o را به آن معرفی می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
  <ItemGroup>
    <!-- EF Core and Sqlite -->
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" />
    <NativeFileReference Include="Data\e_sqlite3.o" />
  </ItemGroup>
</Project>
در اینجا فرض شده‌است که فایل o. حاصل، در پوشه‌ی data قرار دارد. این نوع فایل‌ها توسط NativeFileReferenceها به برنامه معرفی می‌شوند.


سعی در اجرای مجدد برنامه

پس از نصب wasm-tools و ذکر NativeFileReference فوق، اکنون اگر برنامه را اجرا کنیم، برنامه بدون مشکل اجرا خواهد شد:



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorWasmSQLite.zip
اشتراک‌ها
طراحی Entity پایه برای کلاس های Domain

How you shouldn’t implement base classes

public class Entity<T>
{

  public T Id { get; protected set; }

}

Motivation for such code it pretty clear: you have a base class that can be reused across multiple projects. For instance, if there is a web application with GUID Id columns in the database and a desktop app with integer Ids, it might seem a good idea to have the same class for both of them. In fact, this approach introduces accidental complexity because of premature generalization. 

There is no need in using a single base entity class for more than one project or bounded context. Each domain has its unique path, so let it grow independently. Just copy and paste the base entity class to a new project and specify the exact type that will be used for the Id property. 

طراحی Entity پایه برای کلاس های Domain
اشتراک‌ها
برای یادگرفتن بهتر مطلبی، سعی کنید آن‌را به دیگران بیاموزید
When you try to teach something to someone else, you have to go through this process in your own mind. You have to take that mess of data, sort it out, repackage it and organize it in a way that someone else can understand. This process forces you to reorganize the way that data is stored in your own head 
برای یادگرفتن بهتر مطلبی، سعی کنید آن‌را به دیگران بیاموزید
نظرات مطالب
مستند سازی ASP.NET Core 2x API توسط OpenAPI Swagger - قسمت ششم - تکمیل مستندات محافظت از API
خطای مدنظر را در response body درج کنید:
await Context.Response.WriteAsync("This is why you can't log in");
و یا متد HandleChallengeAsync مربوط به AuthenticationHandler را بازنویسی کنید.