نظرات مطالب
مدیریت سراسری خطاها در یک برنامه‌ی Angular
در صورتی که قصد داشته باشیم قسمت error را بر اساس توضیحات بالا حذف کنیم و subscribe  تنها شامل response  باشد ، در زمان ارسال یک فرم در صورت دریافت خطا با کد 400 جهت نمایش خطا به کاربر در فرم چگونه باید عمل کرد
مطالب
بررسی تغییرات Blazor 8x - قسمت چهارم - معرفی فرم‌های جدید تعاملی
در قسمت قبل مشاهده کردیم که چگونه می‌توان کل برنامه را به صورت سراسری، تعاملی کرد تا بتوان توسط آن، Blazor Server سنتی را شبیه سازی نمود؛ اما ... آیا واقعا نیاز است چنین کاری را انجام دهیم؟! چون در این صورت از قابلیت‌‌های جدید SSR به همراه Blazor 8x محروم می‌شویم. اگر کل قابلیت‌های تعاملی مورد نیاز ما در حد یک فرم و ارسال اطلاعات آن به سمت سرور است، می‌توان در Blazor 8x هنوز هم در همان حالت SSR قرار گرفت و از فرم‌های جدید تعاملی آن استفاده کرد تا برای پردازش چنین مواردی، نیازی به برقراری اتصال دائم SignalR نباشد. جزئیات نحوه‌ی کار با اینگونه فرم‌ها را در ادامه بررسی می‌کنیم.


امکان تعریف HTML Forms استاندارد در Blazor 8x

فرم‌های استاندارد HTML، پیش از ظهور جاوااسکریپت و SPAها وجود داشتند (دقیقا همان زمانیکه که فقط مفهوم SSR وجود خارجی داشت) و هنوز هم جزء مهمی از اغلب برنامه‌های وب را تشکیل می‌دهند. با ارائه‌ی دات نت 8 و قابلیت server side rendering آن، کامپوننت‌های برنامه، فقط یکبار در سمت سرور رندر شده و HTML ساده‌ی آن‌ها به سمت مرورگر کاربر بازگشت داده می‌شود. در این حالت، فرم‌های استاندارد HTML، امکان دریافت ورودی‌های کاربر و ارسال داده‌های آن‌ها را به سمت سرور میسر می‌کنند (چون دیگر خبری از اتصال دائم SignalR نیست و باید اطلاعات را به همان نحو استاندارد پروتکل HTTP، به سمت سرور Post کرد). در دات نت 8، دو راه‌حل برای کار با فرم‌ها در برنامه‌های Blazor وجود دارد: استفاده از EditForm خود Blazor و یا استفاده از HTML forms استاندارد و ساده، به همان نحوی که بوده و هست.


روش کار با EditForm در برنامه‌های Blazor SSR

البته ما قصد استفاده از فرم‌های ساده‌ی HTML را در اینجا نداریم و ترجیح می‌دهیم که از همان EditForm استفاده کنیم. EditForms در Blazor بسیار مفید بوده و امکان بایند خواص یک مدل را به اجزای مختلف ورودی‌های تعریف شده‌ی در آن میسر می‌کند و همچنین قابلیت‌هایی مانند اعتبارسنجی و امثال آن‌را نیز به همراه دارد (اطلاعات بیشتر). اما چگونه می‌توان از این امکان در برنا‌مه‌های Blazor SSR نیز استفاده کرد؟
برای این منظور، ابتدا مثالی را به صورت زیر تکمیل می‌کنیم (که بر اساس قالب dotnet new blazor --interactivity Server تهیه شده) و سپس توضیحات آن ارائه خواهد شد:

الف) تهیه یک مدل برای تعریف محل‌های مرتبط با یک سفارش در فایل Models/OrderPlace.cs

using System.ComponentModel.DataAnnotations;

namespace Models;

public record OrderPlace
{
    public Address BillingAddress { get; set; } = new();
    public Address ShippingAddress { get; set; } = new();
}

public class Address
{
    [Required] public string Name { get; set; } = default!;
    public string? AddressLine1 { get; set; }
    public string? AddressLine2 { get; set; }
    public string? City { get; set; }
    [Required] public string PostCode { get; set; } = default!;
}

ب) تهیه‌ی یک کامپوننت Editor برای دریافت اطلاعات آدرس فوق در فایل Components\Pages\Chekout\AddressEntry.razor

@inherits Editor<Models.Address>

<div>
    <label>Name</label>
    <InputText @bind-Value="Value.Name"/>
</div>
<div>
    <label>Address 1</label>
    <InputText @bind-Value="Value.AddressLine1"/>
</div>
<div>
    <label>Address 2</label>
    <InputText @bind-Value="Value.AddressLine2"/>
</div>
<div>
    <label>City</label>
    <InputText @bind-Value="Value.City"/>
</div>
<div>
    <label>Post Code</label>
    <InputText @bind-Value="Value.PostCode"/>
</div>

ج) استفاده از مدل و ادیتور فوق در یک EditForm تغییر یافته برای کار با برنامه‌های Blazor SSR در فایل Components\Pages\Chekout\Checkout.razor

@page "/checkout"

@using Models
@if (!_submitted && PlaceModel != null)
{
    <EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout">
        <DataAnnotationsValidator/>

        <h4>Bill To:</h4>
        <AddressEntry @bind-Value="PlaceModel.BillingAddress"/>

        <h4>Ship To:</h4>
        <AddressEntry @bind-Value="PlaceModel.ShippingAddress"/>

        <button type="submit">Submit</button>
        <ValidationSummary/>
    </EditForm>
}

@if (_submitted && PlaceModel != null)
{
    <div>
        <h2>Order Summary</h2>

        <h3>Shipping To:</h3>
        <dl>
            <dt>Name</dt>
            <dd>@PlaceModel.BillingAddress.Name</dd>
            <dt>Address 1</dt>
            <dd>@PlaceModel.BillingAddress.AddressLine1</dd>
            <dt>Address 2</dt>
            <dd>@PlaceModel.BillingAddress.AddressLine2</dd>
            <dt>City</dt>
            <dd>@PlaceModel.BillingAddress.City</dd>
            <dt>Post Code</dt>
            <dd>@PlaceModel.BillingAddress.PostCode</dd>
        </dl>
    </div>
}

@code {
    bool _submitted;

    [SupplyParameterFromForm]
    public OrderPlace? PlaceModel { get; set; }

    protected override void OnInitialized()
    {
        PlaceModel ??= GetOrderPlace();
    }

    private void SubmitOrder()
    {
        _submitted = true;
    }

    private static OrderPlace GetOrderPlace() =>
        new()
        {
            BillingAddress = new Address
                             {
                                 PostCode = "12345",
                                 Name = "Test 1",
                             },
            ShippingAddress = new Address
                              {
                                  PostCode = "67890",
                                  Name = "Test 2",
                              },
        };

}
توضیحات:
باید بخاطر داشت که این فرم بر اساس حالت Server Side Rendering در اختیار مرورگر کاربر قرار می‌گیرد. یعنی برای بار اول، یک HTML خالص، در سمت سرور بر اساس اطلاعات آن تهیه شده و بازگشت داده می‌شود و زمانیکه به کاربر نمایش داده شد، دیگر برخلاف Blazor Server پیشین، اتصال SignalR ای وجود ندارد تا قابلیت‌های تعاملی آن‌را مدیریت کند. در این حالت اگر به view source صفحه‌ی جاری رجوع کنیم، چنین خروجی قابل مشاهده‌است:
<form method="post">
   <input type="hidden" name="_handler" value="checkout" />
   <input type="hidden" name="__RequestVerificationToken" value="CfDxxx" />
.
.
.
   <button type="submit">Submit</button>
</form>
یعنی زمانیکه این فرم به سمت سرور ارسال می‌شود، همان HTTP POST استاندارد رخ می‌دهد و برای اینکار، نیازی به اتصال وب‌سوکت SignalR ندارد.
این EditForm تعریف شده، دو قسمت اضافه‌تر را نسبت به EditFormهای نگارش‌های قبلی Blazor دارد:
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout">
در اینجا نوع HTTP Method ارسال فرم، مشخص شده و همچنین یک FormName نیز تعریف شده‌است. علت اینجا است که Blazor باید بتواند اطلاعات POST شده و دریافتی در سمت سرور را به کامپوننت متناظری نگاشت کند؛ به همین جهت این نامگذاری، ضروری است.
همانطور که در نحوه‌ی تعریف فرم HTML ای فوق مشخص است، فیلد مخفی handler_، کار متمایز ساختن این فرم را به عهده داشته و از مقدار آن در سمت سرور جهت یافتن کامپوننت متناظر، استفاده خواهد شد.

همچنین برای دریافت و پردازش این اطلاعات در سمت سرور، تنها کافی است خاصیت مرتبط با آن‌را با ویژگی SupplyParameterFromForm مزین کنیم:
[SupplyParameterFromForm]
public OrderPlace? PlaceModel { get; set; }

جریان کاری این فرم به صورت خلاصه به نحو زیر است (که در آن متد OnInitialized دوبار فراخوانی می‌شود و باید به آن دقت داشت):
- در بار اول نمایش این صفحه (با فراخوانی مسیر /checkout در مرورگر)، متد OnInitialized فراخوانی شده و در آن، مقدار شیء PlaceModel نال است.
- بنابراین به متد GetOrderPlace مراجعه کرده و اطلاعاتی را دریافت می‌کند؛ برای مثال، این اطلاعات را از سرویسی می‌خواند.
- پس از پایان هر روال رخدادگردانی در Blazor، در پشت صحنه به صورت خودکار، متد تغییر حالت جاری کامپوننت (متد StateHasChanged) هم فراخوانی می‌شود. این فراخوانی خودکار، باعث رندر مجدد UI آن بر اساس اطلاعات جدید خواهد شد. یعنی قسمت‌های نمایش فرم و نمایش اطلاعات ارسالی، یکبار ارزیابی شده و در صورت برقراری شرط‌ها، نمایش داده می‌شوند.
- در ادامه، کاربر فرم را پر کرده و به سمت سرور POST می‌کند.
- پیش از هر رخ‌دادی، خواص شیء PlaceModel به علت مزین بودن به ویژگی SupplyParameterFromForm، بر اساس اطلاعات ارسالی به سرور، مقدار دهی می‌شوند.
- سپس متد OnInitialized فراخوانی شده و چون اینبار مقدار PlaceModel نال نیست، به متد GetOrderPlace جهت دریافت مقادیر ابتدایی خود مراجعه نمی‌کند. سطر تعریف شده‌ی در متد OnInitialized فقط زمانی سبب مقدار دهی شیء PlaceModel می‌شود که مقدار این شیء، نال باشد (یعنی فقط در اولین بار نمایش صفحه)؛ اما اگر این مقدار توسط پارامتر مزین شده‌ی به SupplyParameterFromForm به علت ارسال داده‌های فرم به سرور، مقدار دهی شده باشد، دیگر به منبع داده‌ی ابتدایی رجوع نمی‌کند.
- چون متد رخ‌دادگردان OnInitialized فراخوانی شده، پس از پایان آن (و فراخوانی خودکار متد StateHasChanged در انتهای آن)، یکبار دیگر کار رندر UI فرم جاری بر اساس اطلاعات جدید، انجام خواهد شد.
- اکنون است که پس از طی این رخ‌دادها، متد رویدادگردان SubmitOrder فراخوانی می‌شود. یعنی زمانیکه این متد فراخوانی می‌شود، شیء PlaceModel بر اساس اطلاعات رسیده‌ی از طرف کاربر، مقدار دهی شده و آماده‌ی استفاده است (برای مثال آماده‌ی ذخیره سازی در بانک اطلاعاتی؛ با فراخوانی سرویسی در اینجا).
- پس از پایان فراخوانی متد رویدادگردان SubmitOrder، به علت تغییر حالت کامپوننت (و فراخوانی خودکار متد StateHasChanged در انتهای آن)، یکبار دیگر نیز کار رندر UI فرم جاری بر اساس اطلاعات جدید انجام خواهد شد. یعنی اینبار قسمت Order Summary نمایش داده می‌شود.


مدیریت تداخل نام‌های HTML Forms در Blazor 8x SSR

تمام فرم‌هایی که به این صورت در برنامه‌های Blazor SSR مدیریت می‌شوند، باید دارای نام منحصربفردی که توسط خاصیت FormName مشخص می‌شود، باشند. برای جلوگیری از این تداخل نام‌ها، کامپوننت جدیدی به نام FormMappingScope معرفی شده‌است که نمونه‌ای از آن‌را در فایل فرضی Components\Pages\Chekout\CheckoutForm.razor تعریف شده‌ی به صورت زیر مشاهده می‌کنید:

@page "/checkout"

<FormMappingScope Name="store-checkout">
    <CheckoutForm />
</FormMappingScope>
در اینجا ابتدا ویژگی page@ کامپوننت CheckoutForm را حذف کرده و آن‌را تبدیل به یک کامپوننت معمولی بدون قابلیت مسیریابی کرده‌ایم. سپس آن‌را توسط کامپوننت FormMappingScope در صفحه‌ای دیگر معرفی و محصور می‌کنیم.
اکنون اگر برنامه را اجرا کرده و خروجی HTML آن‌را بررسی کنیم، به فرم زیر خواهیم رسید:
<form method="post">
   <input type="hidden" name="_handler" value="[store-checkout]checkout" />
   <input type="hidden" name="__RequestVerificationToken" value="CfDxxxxx" />
.
.
.
   <button type="submit">Submit</button>
</form>
همانطور که ملاحظه می‌کنید، اینبار مقدار فیلد مخفی handler_ که کار متمایز ساختن این فرم را به عهده دارد و از آن در سمت سرور جهت یافتن کامپوننت متناظری استفاده می‌شود، با حالتی‌که از کامپوننت FormMappingScope استفاده نشده بود، متفاوت است و نام FormMappingScope را در ابتدای خود به همراه دارد تا به این نحو، از تداخل احتمالی نام‌های فرم‌ها جلوگیری شود.

یک نکته: اگر به تگ‌های فرم HTML ای فوق دقت کنید، به همراه یک anti-forgery token نیز هست که کار تولید و مدیریت آن، به صورت خودکار صورت می‌گیرد و میان‌افزاری نیز برای آن طراحی شده که در فایل Program.cs برنامه، به صورت app.UseAntiforgery بکارگرفته شده‌است.


یک نکته: در Blazor 8x SSR می‌توان بجای EditForm، از همان HTML form متداول هم استفاده کرد

اگر بخواهیم بجای استفاده از EditForm، از فرم‌های استاندارد HTML هم در حالت SSR استفاده کنیم، این کار میسر بوده و روش کار به صورت زیر است:
<form method="post" @onsubmit="SaveData" @formname="MyFormName">
    <AntiforgeryToken />

    <InputText @bind-Value="Name" />

    <button>Submit</button>
</form>
در اینجا ذکر دایرکتیوهای onsubmit@ و formname@ را (شبیه به خواص و رویدادگردان‌های مشابهی در EditForm) به همراه ذکر صریح کامپوننت AntiforgeryToken، مشاهده می‌کنید. در حین استفاده از EditForm، نیازی به درج این کامپوننت نیست و به صورت خودکار اضافه می‌شود.


پردازش فرم‌های GET در Blazor 8x

در حالتی‌که از فرم‌های استاندارد HTML ای استفاده می‌شود، ممکن است method فرم، بجای post، حالت get باشد که نتایج آن به صورت کوئری استرینگ در نوار آدرس مرورگر ظاهر می‌شوند؛ مانند جستجوی گوگل که اشخاص می‌توانند کوئری استرینگ و لینک نهایی را به اشتراک بگذارند. روش پردازش یک چنین فرم‌هایی به صورت زیر است:
@page "/"

<form method="GET">
    <input type="text" name="q"/>
    <button type="submit">Search</button>
</form>


@code {
    [SupplyParameterFromQuery(Name="q")]
    public string SearchTerm { get; set; }
    
    protected override async Task OnInitializedAsync()
    {
       // do something with the search term
    }
}
در اینجا از ویژگی SupplyParameterFromQuery برای دریافت کوئری استرینگ استفاده شده و چون نام پارامتر تعریف شده با نام input فرم یکی نیست، این نام به صورت صریحی توسط خاصیت Name آن مشخص شده‌است.


یک ابتکار! تعاملی کردن قسمتی از صفحه بدون فعالسازی کامل Blazor Server و یا Blazor WASM کامل

این دکمه‌ی قرار گرفته‌ی در یک صفحه‌ی SSR را ملاحظه کنید:
<button class="nav-link border-0" @onclick="BeginSignOut">Log out</button>
در اینجا می‌خواهیم، اگر کاربری بر روی آن کلیک کرد، روال رویدادگردان منتسب به onclick اجرا شود. اما ... اگر در این حالت برنامه را اجرا کرده و بر روی دکمه‌ی Log out کلیک کنیم، هیچ اتفاقی رخ نمی‌دهد! یعنی روال رویدادگران BeginSignOut اصلا اجرا نمی‌شود. علت اینجا است که صفحات SSR، در نهایت یک static HTML بیشتر نیستند و فاقد قابلیت‌های تعاملی، مانند واکنش نشان دادن به کلیک بر روی یک دکمه هستند. برای رفع این مشکل یا می‌توان این قسمت از صفحه را کاملا تعاملی کرد که روش انجام آن‌را در قسمت‌های بعدی با جزئیات کاملی بررسی می‌کنیم و یا ... می‌توان این دکمه را داخل یک فرم جدید تعاملی به صورت زیر محصور کرد:
<EditForm Context="ctx" FormName="LogoutForm" method="post" Model="@Foo" OnValidSubmit="BeginSignOut">
     <button type="submit" class="nav-link border-0">Log out</button>
</EditForm>

@code{
    [SupplyParameterFromForm(Name = "LogoutForm")]
    public string? Foo {  get; set; }

    protected override void OnInitialized() => Foo = "";

    async Task BeginSignOut()
    {
        // TODO: SignOutAsync();
        // TODO: NavigateTo("/authentication/logout");
    }
}
در این حالت چون این فرم، از نوع فرم‌های جدید تعاملی است، برای پردازش آن نیازی به اتصال دائم SignalR و یا فعالسازی یک وب‌اسمبلی نیست. پردازش آن بر اساس استاندارد HTTP Post و فرم‌های آن، صورت گرفته و به این ترتیب می‌توان عملکرد onclick@ کاملا تعاملی را با یک فرم تعاملی جدید، شبیه سازی کرد.


یک نکته: می‌توان حالت post-back مانند فرم‌های تعاملی Blazor 8x را تغییر داد.

به همراه ویژگی‌های جدید مرتبط با صفحات SSR، ویژگی هدایت بهبودیافته هم وجود دارد که جزئیات بیشتر آن‌را در قسمت‌های بعدی این سری بررسی می‌کنیم. برای نمونه اگر مثال این قسمت را اجرا کنید، فرم آن به همراه یک post-back مانند به سمت سرور است که کاملا قابل احساس است؛ این رفتار هرچند استاندارد است، اما بی‌شباهت به برنامه‌های MVC ، Razor pages و یا وب‌فرم‌ها نیست و با فرم‌های بی‌صدا و سریع نگارش‌های قبلی Blazor متفاوت است. در Blazor8x می‌توان این نوع ارسال اطلاعات را Ajax ای هم کرد که به آن enhanced navigation می‌گویند. برای اینکار فقط کافی است ویژگی Enhance را به تگ EditForm اضافه کرد و یا ویژگی جدید data-enhance را به تگ‌های فرم‌های استاندارد HTML ای افزود. پس از آن اگر برنامه را اجرا کنیم، دیگر یک post-back استاندارد وب‌فرم‌ها مشاهده نمی‌شود و رفتار این صفحه بسیار سریع، نرم و روان خواهد بود.
<EditForm Model="PlaceModel" method="post" OnValidSubmit="SubmitOrder" FormName="checkout" Enhance>
در اینجا تنها تغییری که حاصل شده، اضافه شدن ویژگی Enhance به المان EditForm است. این ویژگی به صورت پیش‌فرض غیرفعال است که جزئیات بیشتر آن‌را در قسمت‌های بعدی بررسی خواهیم کرد.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: Blazor8x-Server-Normal.zip
مطالب
اسکریپت گریس مانکی برای تاریخ فارسی بلاگر و ایمیل یاهو

پیرو مطلب قبلی که ارسال کرده بودم، خوب اگر بلاگ‌داری این موارد را اعمال کند، تاریخ شمسی را به وبلاگ خودش اضافه کرده است. اما در مورد دیگران چطور؟ برای این منظور، اسکریپت مطلب ارسالی را به یک اسکریپت گریس مانکی تبدیل کردم که به‌عنوان افزونه‌ای در فایرفاکس نصب خواهد شد و تمامی وبلاگ‌های بلاگری را پوشش می‌دهد (حتی آنهایی که در آدرسشان بلاگر یا بلاگ اسپات نیست).

نحوه استفاده: (همانند روش عنوان شده در این مطلب)
  • سپس به صفحه زیر مراجعه کرده و بر روی دکمه install‌ در بالای صفحه کلیک نمائید.


همچنین ستون تاریخ انگلیسی ایمیل‌های یاهو را هم به تاریخ شمسی تبدیل کردم که جزئی از این مجموعه است (با قالب کلاسیک ایمیل یاهو (نه این قالب جدید) درست کار می‌کند).


مزایای این روش:
  • بلاگ‌دار لازم نیست چیزی را نصب کند.
  • تمام بلاگ‌های مربوط به بلاگر (در دومین و خارج از دومین آن) تحت پوشش قرار می‌گیرند.
  • سریع است. (چون یکبار بر روی مرورگر شما نصب شده است و هیچ فایلی قرار نیست از اینترنت دریافت شود)

شایان ذکر است که گریس مانکی در مرورگرهای دیگر هم پشتیبانی می‌شود.

پ.ن.
اگر علاقمند به برنامه نویسی اسکریپت‌های گریس مانکی هستید، کتابچه‌ای رایگان در این باره را از این آدرس می‌توانید دریافت نمائید.


بازخوردهای دوره
بررسی Semantic Search و FTS Table-valued functions
خیر. فقط بر روی روی خواص و متادیتای فایل‌ها می‌توان جستجوی تکمیلی را انجام داد. نمونه‌ی آن در انتهای بحث تهیه کوئری بر روی ایندکس‌های Full Text Search در قسمت « 9) جستجو بر روی خواص و متادیتای فایل‌ها» و همچنین در مطلب «استفاده از Adobe iFilter برای جستجوی Full Text در فایل‌های PDF» در قسمت PdfSearchPropertyList مثال زده شده‌است.       
نظرات مطالب
سازماندهی برنامه‌های Angular توسط ماژول‌ها
چند نکته‌ی تکمیلی در مورد بهبود تعاریف Shared Module و Core Module

الف) چگونه از import ثانویه‌ی Core Module در سایر ماژول‌ها جلوگیری کنیم؟
Core Module فقط باید در AppModule برنامه import شود و نه در هیچ‌جای دیگری. برای جلوگیری اتفاقی از این مساله می‌توان سازنده‌ای را به شکل زیر به آن اضافه کرد:
@NgModule({
  imports: [CommonModule, RouterModule],
  exports: [], // components that are used in app.component.ts will be listed here.
  declarations: [], // components that are used in app.component.ts will be listed here.
  providers: [BrowserStorageService, AppConfigService] // global singleton services of the whole app will be listed here.
})
export class CoreModule {
  constructor( @Optional() @SkipSelf() core: CoreModule) {
    if (core) {
      throw new Error("CoreModule should be imported ONLY in AppModule.");
    }
  }
};
روش کار به این صورت است که خود CoreModule را به سازنده‌ی همان کلاس تزریق می‌کنیم! اگر وهله‌ای از آن قابل دسترسی بود، یعنی Angular پیشتر این ماژول را import کرده‌است. در این حالت با صدور خطایی این مشکل را گوشزد می‌کنیم.
از همین روش برای تشخیص singleton بودن یک سرویس نیز می‌توان استفاده کرد. خودش را به خودش تزریق می‌کنیم! اگر تزریقی صورت گرفت، یک خطا را صادر می‌کنیم.


ب) چگونه از وهله سازی مجدد سرویس‌های تعریف شده‌ی در Shared Module در سایر ماژول‌ها جلوگیری کنیم؟
هدف از قسمت providers در Shared Module تنها ارائه‌ی سرویس‌هایی جهت کامپوننت‌های اشتراکی آن است؛ وگرنه سرویس‌های سراسری برنامه در CoreModule تعریف می‌شوند و این ماژول ویژه نیز تنها یکبار و آن‌هم در AppModule برنامه import خواهد شد. اما در مورد Shared Module اینطور نیست و اگر این ماژول در یک lazy loaded module استفاده شود، سرویس‌های آن طول عمر متفاوتی را پیدا خواهند کرد (هر lazy loaded module یک injector و یک طول عمر خاص خودش را تعریف می‌کند).
در این حالت برای اینکه سرویس‌های Shared Module فقط در AppModule وهله سازی شوند و نه در هیچ‌جای دیگری، روش کار به صورت ذیل است:
- ابتدا آرایه‌ی providers را از تعاریف NgModule آن حذف می‌کنیم.
- سپس متد ویژه‌ای را به نام forRoot، به کلاس آن اضافه خواهیم کرد:
@NgModule({
  imports: [CommonModule],
  declarations: [], // common and shared components/directives/pipes between more than one module and components will be listed here.
  exports: [CommonModule], // common and shared components/directives/pipes between more than one module and components will be listed here.
  /* No providers here! Since they’ll be already provided in AppModule. */
})
export class SharedModule {
  static forRoot(): ModuleWithProviders {
    // Forcing the whole app to use the returned providers from the AppModule only.
    return {
      ngModule: SharedModule,
      providers: [ /* All of your services here. It will hold the services needed by `itself`. */]
    };
  }
};
متد forRoot به صورت استاتیک تعریف می‌شود و همچنین خروجی از نوع ModuleWithProviders دارد. توسط ModuleWithProviders سبب خواهیم شد، AppModule، این ماژول را به همراه آرایه‌ی providers آن import کند؛ اما سایر ماژول‌ها خیر.
سایر ماژول‌ها چون دسترسی به آرایه‌ی حذف شده‌ی providers این ماژول را ندارند، دیگر نمی‌توانند سرویس‌های آن‌را وهله سازی کنند. اما AppModule با فراخوانی ()SharedModule.forRoot در لیست import خود، تنها یکبار سبب وهله سازی سرویس‌های آن می‌گردد.
بنابراین در اینجا AppModule باید ()SharedModule.forRoot را import کند. سایر ماژول‌ها فقط SharedModule را import می‌کنند (بدون ذکر متد forRoot). به این ترتیب سرویس‌های آن تنها یکبار توسط AppModule در طول عمر برنامه به اشتراک گذاشته می‌شوند و در این حالت تفاوتی نمی‌کند که SharedModule در یک lazy loaded module استفاده شده‌است یا خیر.

روش تعریف متد forRoot توسط سیستم مسیریابی Angular نیز استفاده می‌شود و یک الگوی پذیرفته شده در بین توسعه دهندگان Angular است. برای مثال ()RouterModule.forRoot در AppModule تعریف می‌شود و ()RouterModule.forChild برای سایر ماژول‌ها.

نمونه‌ای از AppModule ، ShardModule و CoreModule
نظرات مطالب
ASP.NET MVC #12
من یه layout دارم که برای پر کردن قسمت منو و یک سری آیتم‌های دیگه از PartialView‌ها استفاده کردم و قصد دارم با Action اون‌ها رو داخل layout نمایش بدم ولی متاسفانه با خطای زیر روبرو میشم:
Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'.
نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
- قسمت «cfg.TokenValidationParameters» در سمت سرور، کار بررسی امضای دیجیتال و سایر مشخصات امنیتی را انجام می‌دهد. بنابراین امکان دستکاری آن در سمت کاربر نیست. اگر دستکاری شود، امضای دیجیتال آن در سمت سرور برگشت می‌خورد و در قسمت cfg.Events لاگ خواهد شد.
- در حافظه‌ی سرور چیزی را نباید ذخیره کنید. سمت سرور برنامه‌های وب، stateless و یا بدون حالت است (یعنی قرار نیست در آن، پس از پایان درخواست، اطلاعاتی از کاربر در حافظه باقی بماند؛ بدلیل محدودیت منابع و مسایل امنیتی). اما این توکن‌ها در سمت کاربر عموما توسط روش local storage ذخیره می‌شوند و هربار از سرور واکشی نمی‌شوند. این توکن‌ها فقط هربار به ازای هر درخواست به منابع محافظت شده‌ی سمت سرور، «باید» از کلاینت به سمت سرور ارسال شوند. در غیراینصورت درخواست بدون توکن، یک درخواست معمولی اعتبارسنجی نشده‌است و ذخیره سازی اطلاعات در حافظه‌ی سرور عملا بی‌معنا است. اطلاعات بیشتر در مورد ذخیره سازی سمت کلاینت: «معرفی Local Storage و چند کتابخانه مرتبط» و «ذخیره سازی اطلاعات در مرورگر توسط برنامه‌های Angular»  
مطالب
ساخت رابط کاربری برای الکترون
قدرت الکترون برگرفته از فناوری وب است و هر آنچه که در آنجا امکان پذیر باشد، در اینجا نیز امکان پذیر است و خصوصیت برنامه‌های دسکتاپ را نیز داراست. الکترون به دلیل بارگذاری فایل‌های html، به شما اجازه می‌دهد تا از ابزارهایی چون بوت استرپ و فریمورک‌ها و کیت‌های مشابهی چون جی‌کوئری و انگیولار، امبر Ember و ... در آن استفاده کنید. ولی با این حال، الکترون نوپا سعی دارد کیت‌های اختصاصی خودش را هم داشته باشد، که در این مقاله به آن‌ها اشاره می‌کنیم.
یکی از این کیت‌ها، فوتون نام دارد. فوتون شامل یک سری css ,sass، فونت و قالب‌های html است که به شما اجازه می‌دهد تا یک برنامه با ظاهری شبیه به برنامه‌های مک را داشته باشید. کامپوننت‌های فوتون شامل tab ها، لیست‌ها، منوی کناری، دکمه‌های معمولی یا جعبه ابزاری و کنترل‌های فرم‌ها می‌شود. با این حال اگر هم دوست ندارید که از این کامپوننت‌ها استفاده کنید، می‌توانید از layout ‌های آن استفاده کند که پنجره‌ی شما را تقسیم بندی می‌کنند.
برای استفاده از فوتون لازم است آن را دانلود کرده و فایل‌های آن را در پروژه‌ی خود کپی کنید. دایرکتوری dist آن شامل یک مثال می‌شود که می‌توانید آن را به داخل دایرکتوری پروژه خود کپی کنید؛ یا خود این دایرکتوری را به عنوان دایرکتوری پروژه تعیین کنید. بعد از آن، فایل‌های موجود در دایرکتوری template را به داخل دایرکتوری والد، یعنی dist انتقال دهید و داخل فایل html، مسیر فایل css را تصحیح نمایید. فقط می‌ماند که الکترون را بر روی این محل نصب کنید، یا اینکه الکترون نصب شده‌ی به صورت عمومی (Global) را در اختیار آن قرار دهید.
بعد از آن ممکن است با خطا مواجه شوید و وقتی فایل اصلی را که در اینجا نام آن app.js است، باز کنید، خطوط زیر را می‌بینید:
var app=require('app');
var BrowserWindow=require('browser-window');
این نوع استفاده از ماژول‌های داخلی، متعلق به نسخه‌های اولیه است و در نسخه‌های اخیر پشتیبانی نمی‌شود. پس بهتر است این خطوط را به صورت‌هایی که قبلا گفته‌ایم تغییر دهید.
سپس برنامه را اجرا کنید تا رابط جدید کاربری را ببینید.
فقط یک مشکلی هست و آن هم این است که باید فریم یا پنجره‌ای را که خود الکترون تولید می‌کند، حذف کنیم برای حذف آن می‌توانید از خصوصیت frame در شیء Browser Window استفاده کنید:
if(process.platform=='darwin')
{
  mainWindow= new BrowserWindow({
    width: 1000,
    height: 500,
    'min-width': 1000,
    'min-height': 500,
    'accept-first-mouse': true,
    'title-bar-style': 'hidden',
    titleBarStyle:'hidden'
  });
}
else {
  new BrowserWindow({
   width: 1000,
   height: 500,
   'min-width': 1000,
   'min-height': 500,
   frame:false
 });
}
در نسخه‌های 10 به بعد مک، از آنجاکه این خصوصیت، نه تنها فریم کرومیوم را حذف میکند، بلکه قابلیت‌هایی چون تغییر اندازه و ... را از آن نیز می‌گیرد، برای همین خصوصیت titleBarStyle را که به دو شکل هم می‌تواند نوشته شود، مورد استفاده قرار می‌دهیم.
حالا اگر برنامه را مجددا اجرا کنید، می‌بینید که قاب‌های دور آن حذف شده‌اند، ولی با چند ثانیه کار کردن متوجه این ایراد می‌شوید که پنجره قابل درگ کردن و جابجایی نمی‌باشد. برای حل آن باید از css کمک بگیریم:
-webkit-app-region: drag
دستور بالا را به هر المانی انتساب دهید، آن المان و فرزندانش قابل درگ خواهند بود، ولی اگر المانی را با این خصوصیت تنظیم کردید، ولی قصد دارید که یکی یا چند عدد از المان‌های فرزند این خاصیت را نداشته باشند، این دستور را به آنان انتساب دهید:
-webkit-app-region: no-drag;
از آنجاکه در این رابط کاربری، نوار عنوان تگ مشخصی دارد:
<header class="toolbar toolbar-header" >
با اضافه کردن این دستور css می‌توانید به آن قابلیت درگ را بدهید:
<header class="toolbar toolbar-header" style="-webkit-app-region: drag">
حالا مجددا برنامه را تست کنید تا نتیجه کار را ببینید.
همانطور که می‌بینید با کمترین زحمت، به چنین رابط کاربری رسیدید. تصویر زیر متعلق به برنامه‌ای است که در دو قسمت قبلی (+ + ) ساختیم و حالا با استفاده از این پکیج، ظاهر آن را تغییر داده‌ایم:



Electron UI Kit
دومین رابط کاربری که معرفی میکنیم در واقع یک کیت از یک سری کامپوننت است که بسیار شبیه به برنامه‌های دسکتاپ طراحی شده و شامل لیست‌ها، گریدها، کنترل‌ها و ... است که در دو فایل استایل، برای ویندوز و مک، مجزا شده‌اند.

Maverix

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