نظرات مطالب
ASP.NET Web API - قسمت اول
نظر کلی من اینه که نمیشه به wcf گفت پیچیده آخه هدفش فرق میکنه.و در ضمن مگه APIحدوداً همون Rest با معماری خیلی ساده‌تر نیست؟
نظرات مطالب
LocalDB چیست؟
- میشه لطفا بگین منظور شما از تحت شبکه دقیقا چیه؟
- برنامه MVC4 که با localdb هست را چگونه میشود روی هاست‌های اشتراکی استفاده کرد؟ آیا نیاز به تنظیم خاص در کانکشن‌استرینگ دارد؟
نظرات مطالب
Blazor 5x - قسمت 19 - کار با فرم‌ها - بخش 7 - نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server
جهت ثبت و دریافت داده‌ها من از MediateR استفاده کردم.
در مرحله اول context را مطابق الوی گفته شده ایجاد کردم:
services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
services.AddScoped<IunitOfWork>(serviceProvider =>
        serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
سپس با توجه به اینکه سرویس‌ها و IunitOfwork هم داخل هندلرها تعریف می‌شوند تنها اینترفیس Idisposable به  IunitOfwork   نسبت داده شده است:
public interface IUnitOfWork : IDisposable
و سپس 
@inherits  OwningComponentBase<IMediator>

تا به حال مشکلی در اجرا دیده نشد تا اینکه در حالت OnParameterAsync که دو مرتبه پشت سر هم اجرا میشود با خطاهای زیر روبرو شد:
 A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.
 For more information on how to avoid threading issues with DbContext

 System.ObjectDisposedException: Cannot access a disposed object.
      Object name: 'IServiceProvider'.
در صورتی که اگر با یک فلگ کنترل شود که در مرتبه‌های بعدی اجرا نشود مشکلی نیست.
در این حالت چند سوال مطرح میباشد:
1- آیا استفاده از MediateR به این شکل صحیح است؟ و اینکه استفاده از OwningComponentBase برای آن تاثیری دارد یا خیر اینکه مستقیم هم Inject شود کفایت میکند.
2- به جهت اینکه Onparameter دوبار صدا زده میشود. نحوه واکشی دیتا در این حالت چگونه است؟
مطالب
مهارت‌های تزریق وابستگی‌ها در برنامه‌های NET Core. - قسمت اول - تزریق وابستگی‌ها در برنامه‌های کنسول
پیشتر با مقدمات تزریق وابستگی‌ها در برنامه‌های ASP.NET Core آشنا شده‌ایم:
در ادامه در طی چند مطلب می‌خواهیم نکات و سناریوهای تکمیلی مرتبط با امکانات تزریق وابستگی‌های توکار برنامه‌های مبتنی بر NET Core. را بررسی کنیم.


تزریق وابستگی‌ها در برنامه‌های کنسول مبتنی بر NET Core.

تزریق وابستگی‌ها، یکی از پرکاربردترین الگوهای طراحی برنامه‌های مدرن است. در نگارش‌های قبلی ASP.NET، به کمک DependencyResolver آن، کتابخانه‌های ثالث کمکی تزریق وابستگی‌ها می‌توانستند خودشان را به سیستم متصل کنند. اینبار ASP.NET Core به همراه IoC Container توکار خودش ارائه شده‌است که این کتابخانه، در خارج از آن، مانند برنامه‌های کنسول نیز قابل استفاده است.


سرویس نمونه‌‌ای برای تزریق آن در یک برنامه‌ی کنسول NET Core.

در پوشه‌ی جدید CoreIocServices، دستور dotnet new classlib را صادر می‌کنیم تا یک پروژه‌ی class library جدید را ایجاد کند. سپس اینترفیس ITestService و یک نمونه پیاده سازی آن‌را به این پروژه اضافه می‌کنیم تا در ادامه بتوانیم تنظیمات تزریق وابستگی‌های آن‌را در یک پروژه‌ی کنسول، ایجاد کنیم:
using System;
using Microsoft.Extensions.Logging;

namespace CoreIocServices
{
    public interface ITestService
    {
        void Run();
    }

    public class TestService : ITestService
    {
        private readonly ILogger<TestService> _logger;

        public TestService(ILogger<TestService> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public void Run()
        {
            _logger.LogWarning("A Warning from the TestService!");
        }
    }
}
در اینجا این سرویس نمونه نیز دارای یک وابستگی تزریق شده‌ی در سازنده‌ی آن است. این وابستگی، همان امکانات توکار logging مربوط به ASP.NET Core است که در برنامه‌های کنسول نیز قابل استفاده است. برای اینکه پروژه قابل کامپایل باشد، نیاز است وابستگی Microsoft.Extensions.Logging را نیز به آن افزود:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
  </ItemGroup>

</Project>


دسترسی به سرویس TestService از طریق تزریق وابستگی‌ها در یک برنامه‌ی کنسول

در ادامه، یک پوشه‌ی جدید را به نام CoreIocSample01 ایجاد کرده و دستور dotnet new console را در آن اجرا می‌کنیم تا یک برنامه‌ی کنسول جدید را ایجاد کند.
سپس اولین قدم برای استفاده‌ی از سرویس TestService از طریق تزریق وابستگی‌ها، افزودن وابستگی‌های مورد نیاز آن است:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CoreIocServices\CoreIocServices.csproj" />
  </ItemGroup>

</Project>
در اینجا بسته‌ی Microsoft.Extensions.DependencyInjection جهت دسترسی به امکانات تزریق وابستگی‌های NET Core. به پروژه اضافه شده و همچنین ارجاعی نیز به پروژه‌ی class library که پیشتر ایجاد کردیم، افزوده شده‌است.
اکنون می‌توانیم همان روشی را که در یک برنامه‌ی ASP.NET Core با ارائه‌ی متد ConfigureServices به صورت از پیش آماده شده برای ما مهیا است، در اینجا نیز پیاده سازی کنیم:
using CoreIocServices;
using Microsoft.Extensions.DependencyInjection;

namespace CoreIocSample01
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);
            var serviceProvider = serviceCollection.BuildServiceProvider();

            var testService = serviceProvider.GetService<ITestService>();
            testService.Run();
        }

        private static void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ITestService, TestService>();
        }
    }
}
کار با تعریف یک ServiceCollection جدید شروع می‌شود. سپس در متد ConfigureServices، همانند کاری که در برنامه‌های ASP.NET Core انجام می‌دهیم، ارتباطات اینترفیس‌ها و پیاده سازی‌های آن‌ها، به همراه طول عمر آن‌ها را تعریف می‌کنیم.
سپس نیاز است بر روی این ServiceCollection، متد BuildServiceProvider فراخوانی شود تا بتوانیم به IServiceProvider دسترسی پیدا کنیم. به آن Dependency Management Container نیز می‌گویند. این Container است که امکان دسترسی به وهله‌ای از ITestService و سپس فراخوانی متد Run آن‌را میسر می‌کند.


مشکل! برنامه‌ی کنسول اجرا نمی‌شود!

اگر سعی کنیم مثال فوق را اجرا کنیم، با استثنای زیر برنامه خاتمه می‌یابد:
Exception has occurred: CLR/System.InvalidOperationException
An unhandled exception of type 'System.InvalidOperationException' occurred in Microsoft.Extensions.DependencyInjection.dll:
'Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[CoreIocServices.TestService]'
while attempting to activate 'CoreIocServices.TestService'.'
عنوان می‌کند که وابستگی تزریق شده‌ی در سازنده‌ی کلاس TestService را نمی‌تواند پیدا کند. علت اینجا است که هرچند ILogger را به سازنده‌ی کلاس سرویس خود اضافه کرده‌ایم، اما هنوز پیاده سازی کننده‌ی آن‌را مشخص نکرده‌ایم. به همین جهت امکان وهله سازی از این کلاس وجود ندارد. عموما در برنامه‌های ASP.NET Core نیازی به تنظیم زیر ساخت logging آن نیست؛ چون این مورد نیز به صورت پیش‌فرض انجام شده‌است. اما در اینجا خیر. به همین جهت دو وابستگی جدید Microsoft.Extensions.Logging.Console و Microsoft.Extensions.Logging.Debug را به پروژه‌ی کنسول اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.2</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\CoreIocServices\CoreIocServices.csproj" />
  </ItemGroup>

</Project>
پس از آن متد ConfigureServices ما جهت تعریف logging در دو حالت دیباگ و کنسول، به صورت زیر تغییر می‌کند:
private static void ConfigureServices(IServiceCollection services)
{
   services.AddLogging(configure => configure.AddConsole().AddDebug());
   services.AddTransient<ITestService, TestService>();
}
اکنون اگر برنامه را اجرا کنیم، خروجی زیر قابل مشاهده خواهد بود:
 CoreIocServices.TestService:Warning: A Warning from the TestService!


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: CoreDependencyInjectionSamples-01.zip
نظرات مطالب
Implementing second level caching in EF code first
- «مثال» این قسمت یک برنامه ویندوزی کنسول است. در جائیکه وب سرور در دسترس نباشه به صورت خودکار به Memory Cache سوئیچ می‌کنه. (البته فرض بر این است که یکبار اجراش کردید یا حداقل خروجی درج شده رو بررسی کردید)
- زمانیکه از لایه سرویس استفاده می‌کنید، استفاده کننده نهایی فقط با یک سری اینترفیس کار می‌کنه نه الزاما پیاده سازی خاص شما. به عبارتی می‌شود mocking رو به سادگی اعمال کرد روی این لایه.
- هدف از این سایت ارائه ایده هست، نه راه حل‌های جهان شمول بی عیب و نقص قابل استفاده در تمام مسایل و مشکلات بشری. همینقدر که ایده‌ای مطرح شده، نکته‌ی جدیدی عنوان شده و کمی تونسته ذهن شما رو درگیر کنه، رسالت خودش رو انجام داده.
- اکثر کارهای این سایت سورس باز هستند. یعنی اگر به این نتیجه رسیدید که می‌تونید کیفیتش رو بهبود ببخشید، لطفا حتما اینکار رو انجام بدید و یک وصله ارائه کنید. البته بعد از اینکار هم حتما ذکر کنید که از چه cache provider جدیدی قرار هست خصوصا در برنامه‌های وب قابل اجرا در IIS که کاربرد اصلی این بحث است، استفاده بشه.
مطالب
Blazor 5x - قسمت 14 - کار با فرم‌ها - بخش 2 - تعریف فرم‌ها و اعتبارسنجی آن‌ها
در ادامه قصد داریم از سرویس زیر که در قسمت قبل تکمیل شد، در یک برنامه‌ی Blazor Server استفاده کنیم:
namespace BlazorServer.Services
{
    public interface IHotelRoomService
    {
        Task<HotelRoomDTO> CreateHotelRoomAsync(HotelRoomDTO hotelRoomDTO);

        Task<int> DeleteHotelRoomAsync(int roomId);

        IAsyncEnumerable<HotelRoomDTO> GetAllHotelRoomsAsync();

        Task<HotelRoomDTO> GetHotelRoomAsync(int roomId);

        Task<HotelRoomDTO> IsRoomUniqueAsync(string name);

        Task<HotelRoomDTO> UpdateHotelRoomAsync(int roomId, HotelRoomDTO hotelRoomDTO);
    }
}


تعریف کامپوننت‌های ابتدایی نمایش لیست اتاق‌ها و ثبت و ویرایش آن‌ها


در ابتدا کامپوننت‌های خالی نمایش لیست اتاق‌ها و همچنین فرم خالی ثبت و ویرایش آن‌ها را به همراه مسیریابی‌های مرتبط، ایجاد می‌کنیم. به همین جهت ابتدا داخل پوشه‌ی Pages، پوشه‌ی جدید HotelRoom را ایجاد کرده و فایل جدید HotelRoomList.razor را با محتوای ابتدایی زیر، به آن اضافه می‌کنیم.
@page "/hotel-room"

<div class="row mt-4">
    <div class="col-8">
        <h4 class="card-title text-info">Hotel Rooms</h4>
    </div>
    <div class="col-3 offset-1">
        <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink>
    </div>
</div>

@code {

}
این کامپوننت در مسیر hotel-room/ قابل دسترسی خواهد بود. بر این اساس، به کامپوننت Shared\NavMenu.razor مراجعه کرده و مدخل منوی آن‌را تعریف می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="hotel-room">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Hotel Rooms
    </NavLink>
</li>

تا اینجا صفحه‌ی ابتدایی نمایش لیست اتاق‌ها، به همراه یک دکمه‌ی افزودن اتاق جدید نیز هست. به همین جهت فایل جدید Pages\HotelRoom\HotelRoomUpsert.razor را به همراه مسیریابی hotel-room/create/ برای تعریف کامپوننت ابتدایی ثبت و ویرایش اطلاعات اتاق‌ها، اضافه می‌کنیم:
@page "/hotel-room/create"

<h3>HotelRoomUpsert</h3>

@code {

}
- واژه‌ی Upsert در مورد فرمی بکاربرده می‌شود که هم برای ثبت اطلاعات و هم برای ویرایش اطلاعات از آن استفاده می‌شود.
- NavLink تعریف شده‌ی در کامپوننت نمایش لیست اتاق‌ها، به مسیریابی کامپوننت فوق اشاره می‌کند.


ایجاد فرم ثبت یک اتاق جدید

برای ثبت یک اتاق جدید نیاز است به مدل UI آن که همان HotelRoomDTO تعریف شده‌ی در قسمت قبل است، دسترسی داشت. به همین جهت در پروژه‌ی BlazorServer.App، ارجاعی را به پروژه‌ی BlazorServer.Models.csproj اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk.Web">
  <ItemGroup>
    <ProjectReference Include="..\BlazorServer.Models\BlazorServer.Models.csproj" />
  </ItemGroup>
</Project>
سپس جهت سراسری اعلام کردن فضای نام آن، یک سطر زیر را به انتهای فایل BlazorServer.App\_Imports.razor اضافه می‌کنیم:
@using BlazorServer.Models
اکنون می‌توانیم کامپوننت Pages\HotelRoom\HotelRoomUpsert.razor را به صورت زیر تکمیل کنیم:
@page "/hotel-room/create"

<div class="row mt-2 mb-5">
    <h3 class="card-title text-info mb-3 ml-3">@Title Hotel Room</h3>
    <div class="col-md-12">
        <div class="card">
            <div class="card-body">
                <EditForm Model="HotelRoomModel">
                    <div class="form-group">
                        <label>Name</label>
                        <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
                    </div>
                </EditForm>
            </div>
        </div>
    </div>
</div>

@code
{
    private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();
    private string Title = "Create";
}
توضیحات:
- در برنامه‌های Blazor، کامپوننت ویژه‌ی EditForm را بجای تگ استاندارد form، مورد استفاده قرار می‌دهیم.
- این کامپوننت، مدل فرم را از فیلد HotelRoomModel که در قسمت کدها تعریف کردیم، دریافت می‌کند. کار آن تامین اطلاعات فیلدهای فرم است.
- سپس در EditForm تعریف شده، بجای المان استاندارد input، از کامپوننت InputText برای دریافت اطلاعات متنی استفاده می‌شود. با bind-value@ در قسمت چهارم این سری بیشتر آشنا شدیم و کار آن two-way data binding است. در اینجا هر اطلاعاتی که وارد می‌شود، سبب به روز رسانی خودکار مقدار خاصیت HotelRoomModel.Name می‌شود و برعکس.

یک نکته: در قسمت قبل، مدل UI را از نوع رکورد C# 9.0 و init only تعریف کردیم. رکوردها، با EditForm و two-way databinding آن سازگاری ندارند (bind-value@ در اینجا) و بیشتر برای کنترلرهای برنامه‌های Web API که یکبار قرار است کار وهله سازی آن‌ها در زمان دریافت اطلاعات از کاربر صورت گیرد، مناسب هستند و نه با فرم‌های پویای Blazor. به همین جهت به پروژه‌ی BlazorServer.Models مراجعه کرده و نوع آن‌ها را به کلاس و init‌ها را به set معمولی تغییر می‌دهیم تا در فرم‌های Blazor هم قابل استفاده شوند.

تا اینجا کامپوننت ثبت اطلاعات یک اتاق جدید، چنین شکلی را پیدا کرده‌است:



تکمیل سایر فیلدهای فرم ورود اطلاعات اتاق

پس از تعریف فیلد ورود اطلاعات نام اتاق، سایر فیلدهای متناظر با HotelRoomDTO را نیز به صورت زیر به EditForm تعریف شده اضافه می‌کنیم که در اینجا از InputNumber برای دریافت اطلاعات عددی و از InputTextArea، برای دریافت اطلاعات متنی چندسطری استفاده شده‌است:
<EditForm Model="HotelRoomModel">
    <div class="form-group">
        <label>Name</label>
        <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
    </div>
    <div class="form-group">
        <label>Occupancy</label>
        <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber>
    </div>
    <div class="form-group">
        <label>Rate</label>
        <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber>
    </div>
    <div class="form-group">
        <label>Sq ft.</label>
        <InputText @bind-Value="HotelRoomModel.SqFt" class="form-control"></InputText>
    </div>
    <div class="form-group">
        <label>Details</label>
        <InputTextArea @bind-Value="HotelRoomModel.Details" class="form-control"></InputTextArea>
    </div>
    <div class="form-group">
        <button class="btn btn-primary">@Title Room</button>
        <NavLink href="hotel-room" class="btn btn-secondary">Back to Index</NavLink>
    </div>
</EditForm>
با این خروجی:



تعریف اعتبارسنجی‌های فیلدهای یک فرم Blazor

در حین تعریف یک فرم، برای واکنش نشان دادن به دکمه‌ی submit، می‌توان رویداد OnSubmit را به کامپوننت EditForm اضافه کرد که سبب فراخوانی متدی در قسمت کدهای کامپوننت جاری خواهد شد؛ مانند فراخوانی متد HandleHotelRoomUpsert در مثال زیر:
<EditForm Model="HotelRoomModel" OnSubmit="HandleHotelRoomUpsert">
</EditForm>

@code
{
    private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();

    private async Task HandleHotelRoomUpsert()
    {

    }
}
هرچند HotelRoomDTO تعریف شده به همراه تعریف اعتبارسنجی‌هایی مانند Required است، اما اگر بر روی دکمه‌ی submit کلیک کنیم، متد HandleHotelRoomUpsert فراخوانی می‌شود. یعنی روال رویدادگردان OnSubmit، صرفنظر از وضعیت اعتبارسنجی مدل فرم، همواره با submit فرم، اجرا می‌شود.
اگر این مورد، مدنظر نیست، می‌توان بجای OnSubmit، از رویداد OnValidSubmit استفاده کرد. در این حالت اگر اعتبارسنجی مدل فرم با شکست مواجه شود، دیگر متد HandleHotelRoomUpsert فراخوانی نخواهد شد. همچنین در این حالت می‌توان خطاهای اعتبارسنجی را نیز در فرم نمایش داد:
<EditForm Model="HotelRoomModel" OnValidSubmit="HandleHotelRoomUpsert">
    <DataAnnotationsValidator />
    @*<ValidationSummary />*@
    <div class="form-group">
        <label>Name</label>
        <InputText @bind-Value="HotelRoomModel.Name" class="form-control"></InputText>
        <ValidationMessage For="()=>HotelRoomModel.Name"></ValidationMessage>
    </div>
    <div class="form-group">
        <label>Occupancy</label>
        <InputNumber @bind-Value="HotelRoomModel.Occupancy" class="form-control"></InputNumber>
        <ValidationMessage For="()=>HotelRoomModel.Occupancy"></ValidationMessage>
    </div>
    <div class="form-group">
        <label>Rate</label>
        <InputNumber @bind-Value="HotelRoomModel.RegularRate" class="form-control"></InputNumber>
        <ValidationMessage For="()=>HotelRoomModel.RegularRate"></ValidationMessage>
    </div>
- در اینجا قسمت‌های تغییر کرده را مشاهده می‌کنید که به همراه درج DataAnnotationsValidator و ValidationMessage‌ها است.
- کامپوننت DataAnnotationsValidator، اعتبارسنجی مبتنی بر data annotations را مانند [Required]، در دامنه‌ی دید یک EditForm فعال می‌کند.
- اگر خواستیم تمام خطاهای اعتبارسنجی را به صورت خلاصه‌ای در بالای فرم نمایش دهیم، می‌توان از کامپوننت ValidationSummary استفاده کرد.
- و یا اگر خواستیم خطاها را به صورت اختصاصی‌تری ذیل هر تکست‌باکس نمایش دهیم، می‌توان از کامپوننت ValidationMessage کمک گرفت. خاصیت For آن از نوع <Expression<System.Func تعریف شده‌است که اجازه‌ی تعریف strongly typed نام خاصیت در حال اعتبارسنجی را به صورتی که مشاهده می‌کنید، میسر می‌کند.



ثبت اولین اتاق هتل

در ادامه می‌خواهیم روال رویدادگردان HandleHotelRoomUpsert را مدیریت کنیم. به همین جهت نیاز به کار با سرویس IHotelRoomService ابتدای بحث خواهد بود. بنابراین در ابتدا به فایل BlazorServer.App\_Imports.razor مراجعه کرده و فضای نام سرویس‌های برنامه را اضافه می‌کنیم:
@using BlazorServer.Services
اکنون امکان تزریق IHotelRoomService را که در قسمت قبل پیاده سازی و به سیستم تزریق وابستگی‌های برنامه معرفی کردیم، پیدا می‌کنیم:
@page "/hotel-room/create"

@inject IHotelRoomService HotelRoomService
@inject NavigationManager NavigationManager


@code
{
    private HotelRoomDTO HotelRoomModel = new HotelRoomDTO();
    private string Title = "Create";

    private async Task HandleHotelRoomUpsert()
    {
        var roomDetailsByName = await HotelRoomService.IsRoomUniqueAsync(HotelRoomModel.Name);
        if (roomDetailsByName != null)
        {
            //there is a duplicate room. show an error msg.
            return;
        }

        var createdResult = await HotelRoomService.CreateHotelRoomAsync(HotelRoomModel);
        NavigationManager.NavigateTo("hotel-room");
    }
}
در اینجا در ابتدا، سرویس IHotelRoomService به کامپوننت جاری تزریق شده و سپس از متدهای IsRoomUniqueAsync و CreateHotelRoomAsync آن، جهت بررسی منحصربفرد بودن نام اتاق و ثبت نهایی اطلاعات مدل برنامه که به فرم جاری به صورت دو طرفه‌ای متصل است، استفاده کرده‌ایم. در نهایت پس از ثبت اطلاعات، کاربر به صفحه‌ی نمایش لیست اتاق‌ها، توسط سرویس توکار NavigationManager، هدایت می‌شود.

اگر پیشتر با ASP.NET Web Forms کار کرده باشید (اولین روش توسعه‌ی برنامه‌های وب در دنیای دات نت)، مدل برنامه نویسی Blazor Server، بسیار شبیه به کار با وب فرم‌ها است؛ البته بر اساس آخرین تغییرات دنیای دانت نت مانند برنامه نویسی async، کار با سرویس‌ها، تزریق وابستگی‌های توکار و غیره.


نمایش لیست اتاق‌های ثبت شده


تا اینجا موفق شدیم اطلاعات یک مدل اعتبارسنجی شده را در بانک اطلاعاتی ثبت کنیم. مرحله‌ی بعد، نمایش لیست اطلاعات ثبت شده‌ی در بانک اطلاعاتی است. بنابراین به کامپوننت HotelRoomList.razor مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
@page "/hotel-room"

@inject IHotelRoomService HotelRoomService

<div class="row mt-4">
    <div class="col-8">
        <h4 class="card-title text-info">Hotel Rooms</h4>
    </div>
    <div class="col-3 offset-1">
        <NavLink href="hotel-room/create" class="btn btn-info">Add New Room</NavLink>
    </div>
</div>

<div class="row mt-4">
    <div class="col-12">
        <table class="table table-bordered table-hover">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Occupancy</th>
                    <th>Rate</th>
                    <th>
                        Sqft
                    </th>
                    <th>

                    </th>
                </tr>
            </thead>
            <tbody>
                @if (HotelRooms.Any())
                {
                    foreach (var room in HotelRooms)
                    {
                        <tr>
                            <td>@room.Name</td>
                            <td>@room.Occupancy</td>
                            <td>@room.RegularRate.ToString("c")</td>
                            <td>@room.SqFt</td>
                            <td></td>
                        </tr>
                    }
                }
                else
                {
                    <tr>
                        <td colspan="5">No records found</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
</div>

@code
{
    private List<HotelRoomDTO> HotelRooms = new List<HotelRoomDTO>();

    protected override async Task OnInitializedAsync()
    {
        await foreach(var room in HotelRoomService.GetAllHotelRoomsAsync())
        {
            HotelRooms.Add(room);
        }
    }
}
توضیحات:
- متد GetAllHotelRoomsAsync، لیست اتاق‌های ثبت شده را بازگشت می‌دهد. البته خروجی آن از نوع <IAsyncEnumerable<HotelRoomDTO است که از زمان C# 8.0 ارائه شد و روش کار با آن اندکی متفاوت است. IAsyncEnumerable‌ها را باید توسط await foreach پردازش کرد.
- همانطور که در مطلب بررسی چرخه‌ی حیات کامپوننت‌ها نیز عنوان شد، متدهای رویدادگران OnInitialized و نمونه‌ی async آن برای دریافت اطلاعات از سرویس‌ها طراحی شده‌اند که در اینجا نمونه‌ای از آن‌را مشاهده می‌کنید.
- پس از تشکیل لیست اتاق‌ها، حلقه‌ی foreach (var room in HotelRooms) تعریف شده، ردیف‌های آن‌را در UI نمایش می‌دهد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-14.zip
نظرات مطالب
کار با اسکنر در برنامه های تحت وب (قسمت دوم و آخر)
یک نکته‌ی تکمیلی

کتابخانه‌ی « DNTScanner.Core » امکان کار با اسکنر را در برنامه‌های NET 4x‌. و همچنین NET Core. ویندوزی میسر می‌کند. روشی که در آن مورد استفاده قرار گرفته، مشکلات مطرح شده‌ی در نظرات مطلب جاری را مانند عدم امکان استفاده‌ی از آن، در سرویس‌های پس‌زمینه را ندارد؛ از این جهت که برای دسترسی به اسکنر، هیچ نوع UI ای را نمایش نمی‌دهد و تمام تنظیمات آن با کدنویسی است.

مثال‌ها
- روش استفاده از آن در برنامه‌های کنسول
- روش استفاده‌ی از آن در یک برنامه‌ی وب ASP.NET Core که قسمت اسکنر آن به صورت یک کلاینت کنسول تهیه شده‌است و ارتباط بین این دو از طریق SignalR.Core برقرار می‌شود.
نظرات مطالب
Blazor 5x - قسمت 19 - کار با فرم‌ها - بخش 7 - نکات ویژه‌ی کار با EF-Core در برنامه‌های Blazor Server
خلاصه‌ی بحث جاری به روشی دیگر (مدیریت صحیح طول عمر DbContext در برنامه‌های Blazor Server)

یک روش دیگر مدیریت طول عمر Context در برنامه‌های Blazor Server که تمام نکات این بحث را به نحو ساده‌ای پوشش می‌دهد، به صورت زیر است:
الف) حتما بجای AddDbContext از AddDbContextFactory که در مطلب فوق توضیح داده شد، استفاده کنید تا بتوان هرجائیکه نیاز است یک Context جدید را به صورت دستی ایجاد کرد، تا از به اشتراک گذاری آن و مشکلات مرتبط با طول عمر اینگونه Contextها، مصون ماند:
// Do NOT do this anymore
//services.AddDbContext(o => o.UseSQlite("filename.db"));

// But instead do this:
services.AddDbContextFactory(o => o.UseSQlite("filename.db"));
ب) سپس بجای انواع و اقسام روش‌های مدیریت طول عمر Scoped و سپس Dispose کردن‌ها، آن‌ها را در سرویس‌های برنامه به نحو زیر پیاده سازی و ساده کنید:
public class MyService
{
   private readonly IDbContextFactorey _contextFactory;

   public MyService(IDbContextFactory contextFactory)
   {
      _contextFactory = contextFactory;
   }

   // create a new context for each operation / unit of work
   public async Task DoSomethingAsync()
   {
      using (var ctx = _contextFactory.CreateDbContext())
      {
         // this using clause contains your unit of work
      }
   }
}
در این حالت با استفاده از IDbContextFactory تزریقی، هر جائیکه که نیاز هست، یک Context جدید و همچنین Scoped، ایجاد و همانجا هم در پایان کار، تخریب و Dispose می‌شود و نیازی به پیاده سازی سرویس‌های IDisposable ندارد. این روشی است که در مثال‌های خود مایکروسافت هم مشاهده می‌شود. سپس کار کردن با سرویس فوق، نیازی به نکات مرتبط با inherits OwningComponentBase ندارد و کاملا به همراه injectهای عادی و متداول است. همچنین هم مطمئن هستیم، Context ای که در هر متد استفاده می‌شود، کاملا جدید و غیر اشتراکی است و در پایان کار همان متد، حتما Dispose می‌شود.
مطالب
React 16x - قسمت 25 - ارتباط با سرور - بخش 4 - یک تمرین
همان مثال backend قسمت 22 را با افزودن وب سرویس‌هایی برای قسمت‌های نمایش لیست فیلم‌ها، ژانرها و سایر صفحات اضافه شده‌ی به برنامه‌ی تکمیل شده‌ی تا قسمت 21، توسعه می‌دهیم. کدهای کامل آن، به علت شباهت و یکی بودن نکات آن با مطلب 22، در اینجا تکرار نخواهند شد و می‌توانید کل پروژه‌ی آن‌را از پیوست انتهای بحث دریافت کنید. سپس فایل dotnet_run.bat آن‌را اجرا کنید تا در آدرس https://localhost:5001 قابل دسترسی شود.


افزودن سرویس httpService.js به برنامه

تا این قسمت، تمام اطلاعات نمایش داده شده‌ی در لیست فیلم‌ها، از سرویس درون حافظه‌ای src\services\fakeMovieService.js و لیست ژانرها از سرویس src\services\fakeGenreService.js، تامین می‌شوند. اکنون در ادامه می‌خواهیم این سرویس‌ها را با سرویس backend یاد شده، جایگزین کنیم تا این برنامه، اطلاعات خودش را از سرور دریافت کند. به همین جهت قبل از هر کاری، سرویس عمومی src\services\httpService.js را که در قسمت قبل توسعه دادیم، به برنامه‌ی نمایش لیست فیلم‌ها نیز اضافه می‌کنیم (فایل آن‌را از پروژه‌ی قبلی کپی کرده و در اینجا paste می‌کنیم)، تا بتوانیم از امکانات آن در اینجا نیز استفاده کنیم. فایل httpService.js، دارای وابستگی‌های خارجی react-toastify و axios است. به همین جهت برای افزودن آن‌ها مراحل زیر را طی می‌کنیم:
- نصب کتابخانه‌های react-toastify و axios از طریق خط فرمان (با فشردن دکمه‌های ctrl+back-tick در VSCode):
> npm i axios --save
> npm i react-toastify --save
سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />


دریافت اطلاعات لیست نمایش ژانرها از سرویس backend

با فراخوانی آدرس https://localhost:5001/api/Genres، می‌توان لیست ژانرهای سینمایی تعریف شده‌ی در سرویس‌های backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
چون نمی‌خواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت می‌کند، جایگزین می‌کنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد می‌کنیم. سپس آن‌را طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
import { apiUrl } from "../config.json";
import http from "./httpService";

export function getGenres() {
  return http.get(apiUrl + "/genres");
}
همچنین در اینجا import وابستگی config.json را نیز مشاهده می‌کنید که در قسمت قبل در مورد آن توضیح دادیم. به همین جهت برای تمیزتر شدن قسمت‌های مختلف برنامه، فایل config.json را در مسیر src\config.json ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
apiUrl به ریشه‌ی URLهای ارائه شده‌ی توسط backend service ما، اشاره می‌کند.

پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
import { getGenres } from "../services/fakeGenreService";
را با سطر جدید زیر، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، خطای زیر را مشاهده خواهید کرد:
Uncaught TypeError: Object is not a function or its return value is not iterable
علت اینجا است که سرویس قبلی fakeGenreService، دارای متد export شده‌ای به نام getGenres بود که یک آرایه‌ی معمولی را بازگشت می‌داد. اکنون این سرویس جدید نیز همان ساختار را دارد، اما اینبار یک Promise را بازگشت می‌دهد. به همین جهت متد componentDidMount را به صورت زیر اصلاح می‌کنیم:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ _id: "", name: "All Genres" }, ...data];
    this.setState({ movies: getMovies(), genres });
  }
متد getGenres باید await شود تا نتیجه‌ی آن توسط خاصیت data شیء بازگشتی از آن، قابل دسترسی شود. با این تغییر، نیاز است این متد را نیز به صورت async معرفی کرد.


دریافت اطلاعات لیست فیلم‌ها از سرویس backend

پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونه‌ی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
import { apiUrl } from "../config.json";
import http from "./httpService";

const apiEndpoint = apiUrl + "/movies";

function movieUrl(id) {
  return `${apiEndpoint}/${id}`;
}

export function getMovies() {
  return http.get(apiEndpoint);
}

export function getMovie(movieId) {
  return http.get(movieUrl(movieId));
}

export function saveMovie(movie) {
  if (movie.id) {
    return http.put(movieUrl(movie.id), movie);
  }

  return http.post(apiEndpoint, movie);
}

export function deleteMovie(movieId) {
  return http.delete(movieUrl(movieId));
}
سپس شروع به اصلاح کامپوننت movies می‌کنیم.
ابتدا دو متد دریافت لیست فیلم‌ها و حذف یک فیلم را که در این کامپوننت استفاده شده‌اند، import می‌کنیم:
import { getMovies, deleteMovie } from "../services/movieService";
بعد متد getMovies پیشین، که یک آرایه را بازگشت می‌داد، توسط متد جدیدی که یک Promise را بازگشت می‌دهد، جایگزین می‌شود:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ id: "", name: "All Genres" }, ...data];

    const { data: movies } = await getMovies();
    this.setState({ movies, genres });
  }
همچنین مدیریت حذف رکوردها را نیز به صورت زیر با پیاده سازی «به‌روز رسانی خوشبینانه UI» که در قسمت قبل در مورد آن بیشتر بحث شد، تغییر می‌دهیم. در این حالت فرض بر این است که به احتمال زیاد،  await deleteMovie با موفقیت به پایان می‌رسد. بنابراین بهتر است UI را ابتدا به روز رسانی کنیم تا کاربر حس کار کردن با یک برنامه‌ی سریع را داشته باشد:
  handleDelete = async movie => {
    const originalMovies = this.state.movies;

    const movies = originalMovies.filter(m => m.id !== movie.id);
    this.setState({ movies });

    try {
      await deleteMovie(movie.id);
    } catch (ex) {
      if (ex.response && ex.response.status === 404) {
        console.log(ex);
        toast.error("This movie has already been deleted.");
      }

      this.setState({ movies: originalMovies }); //undo changes
    }
  };
ابتدا ارجاعی را از state قبلی ذخیره می‌کنیم تا در صوت بروز خطایی در سطر await deleteMovie، بتوانیم مجددا state را به حالت اول آن بازگردانیم. به همین منظور پیاده سازی «به‌روز رسانی خوشبینانه UI»، حتما نیاز به درج صریح try/catch را دارد. برای نمایش خطاهای ویژه‌ی 404 نیز از یک toast استفاده شده که نیاز به import زیر را دارد:
import { toast } from "react-toastify";
سایر خطاهای رخ داده، توسط interceptor درج شده‌ی در سرویس http، به صورت خودکار پردازش می‌شوند.


اتصال فرم ثبت و ویرایش یک فیلم به backend server

تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شده‌ی در صفحه، به صفحه‌ی not-found هدایت می‌شویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
import { getGenres } from "../services/fakeGenreService";
import { getMovie, saveMovie } from "../services/fakeMovieService";
قبلی را با نمونه‌ها‌ی جدیدی که با سرور کار می‌کنند، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
import { getMovie, saveMovie } from "../services/movieService";
سپس ارجاعات به این سه متد import شده را با await، همراه کرده و متد اصلی را به صورت async معرفی می‌کنیم:
  async componentDidMount() {
    const { data: genres } = await getGenres();
    this.setState({ genres });

    const movieId = this.props.match.params.id;
    if (movieId === "new") return;

    const { data: movie } = await getMovie(movieId);
    if (!movie) return this.props.history.replace("/not-found");

    this.setState({ data: this.mapToViewModel(movie) });
  }
البته می‌توان جهت بهبود کیفیت کدها، از متد componentDidMount، دو متد با مسئولیت‌های مجزای دریافت ژانرها (populateGenres) و سپس نمایش فرم اطلاعات فیلم (populateMovie) را استخراج کرد:
  async populateGenres() {
    const { data: genres } = await getGenres();
    this.setState({ genres });
  }

  async populateMovie() {
    try {
      const movieId = this.props.match.params.id;
      if (movieId === "new") return;

      const { data: movie } = await getMovie(movieId);
      this.setState({ data: this.mapToViewModel(movie) });
    } catch (ex) {
      if (ex.response && ex.response.status === 404)
        this.props.history.replace("/not-found");
    }
  }

  async componentDidMount() {
    await this.populateGenres();
    await this.populateMovie();
  }
در متد populateMovie، اگر movieId اشتباهی وارد شود و یا کلا عملیات دریافت اطلاعات، با شکست مواجه شود، کاربر را به صفحه‌ی not-found هدایت می‌کنیم. یعنی وجود try/catch در اینجا ضروری است. چون اگر movieId اشتباهی وارد شود، اینبار دیگر خطوط بعدی اجرا نمی‌شوند و در همان سطر await getMovie، یک استثناء صادر شده و کار خاتمه پیدا می‌کند. بنابراین نیاز داریم بتوانیم این استثنای احتمالی را مدیریت کرده و کاربر را به صفحه‌ی not-found هدایت کنیم.
پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت می‌داد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت می‌گرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.

مرحله‌ی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام می‌شود:
  doSubmit = async () => {
    await saveMovie(this.state.data);

    this.props.history.push("/movies");
  };


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip
مطالب
مدیریت اعمال آغازین در برنامه‌های Angular
در برنامه‌های کاربردی بر پایه Angular گاها نیاز است اعمالی را قبل از بارگذاری آغازین نرم افزار انجام دهید. این موارد می‌توانند خواندن اطلاعات پیکربندی از یک فایل json. باشند و یا گرفتن داده‌هایی از سرور بک‌اند و استفاده از آنها برای ایجاد محدودیت در بعضی از قسمتها.
از +Angular 4 با بکار گیری توکن APP_INITIALIZER در ماژول آغازین برنامه (app.module)، امکان معرفی و ثبت تابعی را جهت اجرا، در ابتدای چرخه حیات برنامه، خواهیم داشت. برای روشن شدن مطلب، مثالی از خواندن اطلاعات پیکربندی واقع در یک فایل جیسون را در ذیل پیاده سازی می‌کنیم.
در بسیاری موارد نیاز داریم که پس از بیلد پروژه، امکان تغییر آدرس نهایی هاست سمت سرور را داشته باشیم و بر اساس آن بتوانیم Web Api Url‌ها را در سرویس‌های Angular به روز کنیم. برای این کار فایلی را به نام config.json با محتویات ذیل ایجاد می‌کنیم (آدرس هاست را مطابق سرور خود، تغییر دهید): 
{
  "host": "http://localhost:8008"
}

سپس  سرویسی که وظیفه‌ی خواندن اطلاعات را بر عهده دارد، ترجیحا در بخش Core Modules پروژه با کد ذیل، ایجاد می‌کنیم:
import { Observable } from 'rxjs/Observable';
import { Inject, Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class AppConfigService {
    constructor(private http: Http) {   }
  private config: Object = null;
    get apiRoot() {
      return this.getProperty('host'); // <--- THIS GETS CALLED FIRST
    }
    load(): Promise<any> {
      console.log('get user called');
      const promise = this.http.get('assets/config.json').map((res) => res.json()).toPromise();
      promise.then(config => {
        this.config = config;     // <--- THIS RESOLVES AFTER
        console.log(this.config);
      });
    return promise;
  }
    private getProperty(property: string): any {
      //noinspection TsLint
      if (!this.config) {
        throw new Error('Attempted to access configuration property before configuration data was loaded, please implemented.');
      }

      if (!this.config[property]) {
        throw new Error(`Required property ${property} was not defined within the configuration object. Please double check the
        assets/config.json file`);
      }

      return this.config[property];
    }

}

حال در app.module  توسط APP_INITIALIZER، متد Load سرویس را برای مقدار دهی خاصیت apiRoot سرویس صدا می‌زنیم. بنابراین ابتدا تابع init را تکمیل می‌کنیم: 
export function init(config: AppConfigService) {
  return () => {
    return  config.load(); 
  };
}

سپس در قسمت Providers ماژول آغازین (app.module)، مانند کد زیر اقدام می‌کنیم:
  Providers:[
…,
AppConfigService,
{
      provide: APP_INITIALIZER,
      useFactory: init,
      multi: true,
      deps: [AppConfigService]
   }
…,

]

حال می‌توانیم سرویس AppConfigService را در متد سازنده‌ی سرویس‌های خود تزریق کرده و از خاصیت apiRoot  جهت به روز رسانی آدرس‌های خود استفاده کنیم:
@Injectable()
export class DashboardService {
  private tagUrl = '';
  constructor(private http: Http,private AppConfig:AppConfigService) {
      this.tagtUrl = this.AppConfig.apiRoot+"/myApiUrl";
….
}

نکته تکمیلی: می‌توان چندین تابع را به همین روش در نقطه‌ی آغازین برنامه، جهت اجرا معرفی کرد. کافی است useFactory را به نام تابع مورد نظر خود و خاصیت deps را که اشاره به نوع پارامتر ورودی تابع دارد (در اینجا سرویس AppConfigService) مقدار دهی کنید: 
    {
      provide: APP_INITIALIZER,
      useFactory: init,
      multi: true,
      deps: [AppConfigService]
    },
  {
    provide: APP_INITIALIZER,
    useFactory: initIdentity,
    multi: true,
    deps: [IdentityService]
  },
  AppConfigService,
  IdentityService,