نظرات مطالب
خلاص شدن از شر deep null check
متاسفانه روش فوق کد نویسی را تا حد زیادی تحت تاثیر قرار می‌دهد، مگر این که روش استفاده از متد الحاقی شما را به خوبی متوجه نشده باشم
به مثال زیر دقت کنید:
public class Customer
    {
        public CustomerInfo Info { get; set; }
        public Int32 GetNameLength()
        {
            return this.IfNotDefault(city => city.Info)
                .IfNotDefault(info => info.CityInfo)
                .IfNotDefault(cityInfo => cityInfo.Name)
                .IfNotDefault(name => name.Length);
        }
    }
    public class CustomerInfo
    {
        public CustomerCityInfo CityInfo { get; set; }
    }
    public class CustomerCityInfo
    {
        public String Name { get; set; }
    }
و برای استفاده داریم:
            Customer customer = new Customer();
            String cityName = customer
                .IfNotDefault(cust => cust.Info)
                .IfNotDefault(info => info.CityInfo)
                .IfNotDefault(city => city.Name);
            Int32 length = customer.GetNameLength();
در حالی که با متد الحاقی زیر داریم
public static TValue GetValue<TObj, TValue>(this TObj obj, Func<TObj, TValue> member, TValue defaultValueOnNull = default(TValue))
        {
            if (member == null)
                throw new ArgumentNullException("member");

            if (obj == null)
                throw new ArgumentNullException("obj");

            try
            {
                return member(obj);
            }
            catch (NullReferenceException)
            {
                return defaultValueOnNull;
            }
        }  
            تعریف ساده‌تر کلاس
   public class Customer
    {
        public CustomerInfo Info { get; set; }

        public Int32 GetNameLength()
        {
            return this.Info.CityInfo.Name.Length;
        }
    }

    public class CustomerInfo
    {
        public CustomerCityInfo CityInfo { get; set; }
    }

    public class CustomerCityInfo
    {
        public String Name { get; set; }
    }  
و سادگی در استفاده
             Customer customer = new Customer();

            String cityName = customer.GetValue(cust => cust.Info.CityInfo.Name, "Not Selected");

            Int32 i = customer.GetValue(cust => cust.GetNameLength());    
شاید بگویید استفاده از Try-Catch سیستم را کند می‌کند، البته نه در آن حدی که فکر می‌کنید، و اگر قسمتی از کد شما به تعداد زیادی در بازه‌ی زمانی کوتاه فراخوانی می‌شود، می‌توانید آنرا به صورت کاملا عادی بنویسید، چون واقعا تعداد این شرایط زیاد نیست و این مورد سناریوی فراگیری نیست، در عوض خوانایی کد بسیار بسیار بالاتر از حالات عادی است.
در ضمن دقت کنید که تا زمانی که خطای NullReference رخ ندهد، سرعت سیستم در حد همان حداقل نیز کاهش نمی‌یابد، بدین جهت که بسیاری از افراد فکر می‌کنند Try-Catch نوشتن به خودی خود برنامه را کند می‌کند، ولی این رخ دادن خطا و جمع آوری StackTrace و ... است که برنامه را کند می‌کند، که شاید در خیلی از موارد اصلا رخ ندهد.
البته کدهای نوشته صرفا نمونه کد است، به هیچ وجه اصول طراحی در آن رعایت نشده است، بلکه سعی کرده ام مثال واضح‌تری بزنم
موفق و پایدار باشید
مطالب
نمایش ساختارهای درختی در Blazor
یکی از نکات جالب رندر کامپوننت‌ها در Blazor، امکان فراخوانی بازگشتی آن‌ها است؛ یعنی یک کامپوننت می‌تواند خودش را نیز فراخوانی کند. از همین قابلیت می‌توان جهت نمایش ساختارهای درختی، مانند مدل‌های خود ارجاع دهنده‌ی EF استفاده کرد.


مدل برنامه، جهت تامین داده‌های خود ارجاع دهنده و درختی

فرض کنید قصد داریم لیستی از کامنت‌های تو در تو را مدل سازی کنیم که در آن هر کامنت، می‌تواند چندین کامنت تا بی‌نهایت سطح تو در تو را داشته باشد:
namespace BlazorTreeView.ViewModels;

public class Comment
{
    public IList<Comment> Comments = new List<Comment>();
    public string? Text { set; get; }
}
برای نمونه بر اساس این مدل، منبع داده‌ی فرضی زیر را تهیه می‌کنیم:
using BlazorTreeView.ViewModels;

namespace BlazorTreeView.Pages;

public partial class TreeView
{
    private IReadOnlyDictionary<string, object> ChildrenHtmlAttributes { get; } =
        new Dictionary<string, object>(StringComparer.Ordinal)
        {
            { "style", "list-style: none;" },
        };

    private IList<Comment> Comments { get; } =
        new List<Comment>
        {
            new()
            {
                Text = "پاسخ یک",
            },
            new()
            {
                Text = "پاسخ دو",
                Comments =
                    new List<Comment>
                    {
                        new()
                        {
                            Text = "پاسخ اول به پاسخ دو",
                            Comments =
                                new List<Comment>
                                {
                                    new()
                                    {
                                        Text = "پاسخی به پاسخ اول پاسخ دو",
                                    },
                                },
                        },
                        new()
                        {
                            Text = "پاسخ دوم به پاسخ دو",
                        },
                    },
            },
            new()
            {
                Text = "پاسخ سوم",
            },
        };
}
این قطعه کد partial class که مربوط به فایل TreeView.razor.cs برنامه‌است، در حقیقت کدهای پشت صحنه‌ی کامپوننت مثال TreeView.razor است که در ادامه آن‌را توسعه خواهیم داد. در نهایت قرار است بتوانیم آن‌را به صورت زیر رندر کنیم:



طراحی کامپوننت DntTreeView

برای اینکه بتوانیم به یک کامپوننت با قابلیت استفاده‌ی مجدد بررسیم، کدهای نمایش اطلاعات تو در تو و درختی را توسط کامپوننت سفارشی DntTreeView پیاده سازی خواهیم کرد. پیشنیازهای آن نیز به صورت زیر است:
- این کامپوننت باید جنریک باشد؛ یعنی باید به صورت زیر شروع شود:
/// <summary>
///   A custom DntTreeView
/// </summary>
public partial class DntTreeView<TRecord>
{
چون باید بتوان یک لیست جنریک <IEnumerable<TRecord را به آن، جهت رندر ارسال کرد و قرار نیست این کامپوننت، تنها به شیء سفارشی Comment مثال جاری ما وابسته باشد. بنابراین اولین خاصیت آن، شیء جنریک Items است که لیست کامنت‌ها/عناصر را دریافت می‌کند:
    /// <summary>
    ///     The treeview's self-referencing items
    /// </summary>
    [Parameter]
    public IEnumerable<TRecord>? Items { set; get; }
- هنگام رندر هر آیتم کامنت باید بتوان یک قالب سفارشی را از کاربر دریافت کرد. نمی‌خواهیم صرفا برای مثال Text شیء Comment فوق را به صورت متنی و ساده نمایش دهیم. می‌خواهیم در حین رندر، کل شیء TRecord جاری را به مصرف کننده ارسال و یک قالب سفارشی را از آن دریافت کنیم. یعنی باید یک RenderFragment جنریک را به صورت زیر نیز داشته باشیم تا مصرف کننده بتواند TRecord در حال رندر را دریافت و قالب Htmlای خودش را بازگشت دهد:
    /// <summary>
    ///     The treeview item's template
    /// </summary>
    [Parameter]
    public RenderFragment<TRecord>? ItemTemplate { set; get; }
- همچنین همیشه باید به فکر عدم وجود اطلاعاتی برای نمایش نیز بود. به همین جهت بهتر است قالب دیگری را نیز از مصرف کننده برای اینکار درخواست کنیم و نحوه‌ی رندر سفارشی این قسمت را نیز به مصرف کننده واگذار کنیم:
    /// <summary>
    ///     The content displayed if the list is empty
    /// </summary>
    [Parameter]
    public RenderFragment? EmptyContentTemplate { set; get; }
- زمانیکه با شیء از پیش تعریف شده‌ی Comment این مثال کار می‌کنیم، کاملا مشخص است که خاصیت Comments آن تو در تو است:
public class Comment
{
    public IList<Comment> Comments = new List<Comment>();
    public string? Text { set; get; }
}
اما زمانیکه با یک کامپوننت جنریک کار می‌کنیم، نیاز است از مصرف کننده، نام این خاصیت تو در تو را به نحو واضحی دریافت کنیم؛ به صورت زیر:
    /// <summary>
    ///     The property which returns the children items
    /// </summary>
    [Parameter]
    public Expression<Func<TRecord, IEnumerable<TRecord>>>? ChildrenSelector { set; get; }
دلیل استفاده از Expression Funcها را در مطلب «static reflection» می‌توانید مطالعه کنید. زمانیکه قرار است از کامپوننت DntTreeView استفاده کنیم، ابتدا نوع جنریک آن‌را مشخص می‌کنیم، سپس لیست اشیاء ارسالی به آن‌را و در ادامه با استفاده از ChildrenSelector به صورت زیر، مشخص می‌کنیم که خاصیت Comments است که به همراه Children می‌باشد و تو در تو است:
        <DntTreeView
            TRecord="Comment"
            Items="Comments"
            ChildrenSelector="m => m.Comments"
و مرسوم است جهت بالابردن کارآیی Expression Funcها، آن‌ها را کامپایل و کش کنیم که نمونه‌ای از روش آن‌را به صورت زیر مشاهده می‌کنید:
public partial class DntTreeView<TRecord>
{
    private Expression? _lastCompiledExpression;
    internal Func<TRecord, IEnumerable<TRecord>>? CompiledChildrenSelector { private set; get; }

    // ...

    protected override void OnParametersSet()
    {
        if (_lastCompiledExpression != ChildrenSelector)
        {
            CompiledChildrenSelector = ChildrenSelector?.Compile();
            _lastCompiledExpression = ChildrenSelector;
        }
    }
}
تا اینجا ساختار کدهای پشت صحنه‌ی DntTreeView.razor.cs مشخص شد. اکنون UI این کامپوننت را به صورت زیر تکمیل می‌کنیم:
@namespace BlazorTreeView.Pages.Components
@typeparam TRecord

@if (Items is null || !Items.Any())
{
    @EmptyContentTemplate
}
else
{
    <CascadingValue Value="this">
        <ul @attributes="AdditionalAttributes">
            @foreach (var item in Items)
            {
                <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/>
            }
        </ul>
    </CascadingValue>
}
در ابتدای کار، اگر آیتمی برای نمایش وجود نداشته باشد، EmptyContentTemplate دریافتی از استفاده کننده را رندر می‌کنیم. در غیراینصورت، حلقه‌ای را بر روی لیست Items ایجاد کرده و آن‌‌ها را یکی نمایش می‌دهیم. این نمایش، نکات زیر را به همراه دارد:
- نمایش توسط کامپوننت دومی به نام DntTreeViewChildrenItem انجام می‌شود که آن‌‌هم جنریک است و شیء item جاری را توسط خاصیت ParentItem دریافت می‌کند.
- در اینجا یک CascadingValue اشاره کننده به شیء this را هم مشاهده می‌کنید. این روش، یکی از روش‌های اجازه دادن دسترسی به خواص و امکانات یک کامپوننت والد، در کامپوننت‌های فرزند است که در ادامه از آن استفاده خواهیم کرد.


تکمیل کامپوننت بازگشتی DntTreeViewChildrenItem.razor

اگر به حلقه‌ی foreach (var item in Items) در کامپوننت DntTreeView.razor دقت کنید، یک سطح را بیشتر پوشش نمی‌دهد؛ اما کامنت‌های ما چندسطحی و تو در تو هستند و عمق آن‌ها هم مشخص نیست. به همین جهت نیاز است به نحوی بتوان یک طراحی recursive و بازگشتی را در کامپوننت‌های Blazor داشت که خوشبختانه این مورد پیش‌بینی شده‌است و هر کامپوننت Blazor، می‌تواند خودش را نیز فراخوانی کند:
@namespace BlazorTreeView.Pages.Components
@typeparam TRecord

<li @attributes="@SafeOwnerTreeView.ChildrenHtmlAttributes" @key="ParentItem?.GetHashCode()">
    @if (SafeOwnerTreeView.ItemTemplate is not null && ParentItem is not null)
    {
        @SafeOwnerTreeView.ItemTemplate(ParentItem)
    }
    @if (Children is not null)
    {
        <ul>
            @foreach (var item in Children)
            {
                <DntTreeViewChildrenItem TRecord="TRecord" ParentItem="item"/>
            }
        </ul>
    }
</li>
این‌ها کدهای DntTreeViewChildrenItem.razor هستند که در آن، ابتدا ItemTemplate دریافتی از والد یا همان DntTreeView.razor رندر می‌شود. سپس به کمک CompiledChildrenSelector ای که عنوان شد، یک شیء Children را تشکیل داده و آن‌را به خودش (فراخوانی مجدد DntTreeViewChildrenItem در اینجا)، ارسال می‌کند. این فراخوانی بازگشتی، سبب رندر تمام سطوح تو در توی شیء جاری می‌شود.

کدهای پشت صحنه‌ی این کامپوننت یعنی فایل DntTreeViewChildrenItem.razor.cs به صورت زیر است:
/// <summary>
///     A custom DntTreeView
/// </summary>
public partial class DntTreeViewChildrenItem<TRecord>
{
    /// <summary>
    ///     Defines the owner of this component.
    /// </summary>
    [CascadingParameter]
    public DntTreeView<TRecord>? OwnerTreeView { get; set; }

    private DntTreeView<TRecord> SafeOwnerTreeView =>
        OwnerTreeView ??
        throw new InvalidOperationException("`DntTreeViewChildrenItem` should be placed inside of a `DntTreeView`.");

    /// <summary>
    ///     Nested parent item to display
    /// </summary>
    [Parameter]
    public TRecord? ParentItem { set; get; }

    private IEnumerable<TRecord>? Children =>
        ParentItem is null || SafeOwnerTreeView.CompiledChildrenSelector is null
            ? null
            : SafeOwnerTreeView.CompiledChildrenSelector(ParentItem);
}
با استفاده از یک پارامتر از نوع CascadingParameter، می‌توان به اطلاعات شیء CascadingValue ای که در کامپوننت والد DntTreeView.razor قرا دادیم، دسترسی پیدا کنیم. سپس یکبار هم بررسی می‌کنیم که آیا نال هست یا خیر. یعنی قرار نیست که این کامپوننت فرزند، درجائی به صورت مستقیم استفاده شود. فقط قرار است داخل کامپوننت والد فراخوانی شود. به همین جهت اگر این CascadingParameter نال بود، یعنی این کامپوننت فرزند، به اشتباه فراخوانی شده و با صدور استثنائی این مساله را گوشزد می‌کنیم. اکنون که به SafeOwnerTreeView یا همان نمونه‌ای از شیء والد دسترسی پیدا کردیم، می‌توانیم پارامتر CompiledChildrenSelector آن‌را نیز فراخوانی کرده و توسط آن، به شیء تو در توی جدیدی در صورت وجود، جهت رندر بازگشتی آن رسید.
یعنی این کامپوننت ابتدا ParentItem، یا اولین سطح ممکن و در دسترس را رندر می‌کند. سپس با استفاده از Expression Func مهیای در کامپوننت والد، شیء فرزند را در صورت وجود یافته و سپس به صورت بازگشتی آن‌را با فراخوانی مجدد خودش ، رندر می‌کند.


روش استفاده از کامپوننت DntTreeView

اکنون که کار توسعه‌ی کامپوننت جنریک DntTreeView پایان یافت، روش استفاده‌ی از آن به صورت زیر است:
<div class="card" dir="rtl">
    <div class="card-header">
        DntTreeView
    </div>
    <div class="card-body">
        <DntTreeView
            TRecord="Comment"
            Items="Comments"
            ChildrenSelector="m => m.Comments"
            style="list-style: none;"
            ChildrenHtmlAttributes="ChildrenHtmlAttributes">
            <ItemTemplate Context="record">
                <div class="card mb-1">
                    <div class="card-body">
                        <span>@record.Text</span>
                    </div>
                </div>
            </ItemTemplate>
            <EmptyContentTemplate>
                <div class="alert alert-warning">
                    There is no item to display!
                </div>
            </EmptyContentTemplate>
        </DntTreeView>
    </div>
</div>
همانطور که مشاهده می‌کنید، چون کامپوننت جنریک است، باید نوع TRecord را که در مثال ما، شیء Comment است، مشخص کرد. سپس لیست نظرات، خاصیت تو در تو، قالب سفارشی نمایش Text نظرات (با توجه به Context دریافتی که امکان دسترسی به شیء جاری در حال رندر را میسر می‌کند) و همچنین قالب سفارشی نبود اطلاعاتی برای نمایش را تعریف می‌کنیم.

کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorTreeView.zip
کامپوننت توسعه یافته‌ی در اینجا در هر دو حالت Blazor WASM و Blazor Server کار می‌کند.
نظرات مطالب
OpenCVSharp #5
سلام؛ ممنون از مطلب خوبتون. من از کتابخانه‌های opncv و emgu برای خوندن تصویر از وب کم و پردازش تصویر استفاده میکنم. مشکل من سرعت پایین برنامه در رزولوشن بالای وب کم است هر چه رزولوشن بالاتر میره سرعت برنامه من کمتر میشه. مثلا تو یه سیستم corei7 با ram 8 gig من 61 درصد استفاده از cpu دارم و فیلم نمایش داده شده در برنامه از محیط واقعی عقب‌تر است. من در رویداد Application.Idle   فرم این کد را قرار دادم:
 public void Application_Idle(object sender, EventArgs e) 
        {
            if (_capture != null)
            {
                try
                {
                    frame = _capture.QueryFrame();
                    Pic.Image = frame.ToBitmap();
                    //frame.ToBitmap(dst: (Bitmap)Pic.Image);
                }
                catch (NullReferenceException excpt)
                {
                    MessageBox.Show(excpt.Message);// you can also show any suitable message
                }
            }
        }
شما فرمودید برای به حداقل رسانی به روز رسانی‌های بعدی picture box بهتر است از متد ToBitmap به شکل زیر کمک گرفت: 
iplImage.ToBitmap(dst: (Bitmap)pictureBox.Image);
ولی dst رو فرم نمیشناسه و اینکه آیا رویداد idle رویداد مناسبی برای این کار هست؟ نظرتون درباره سرعت پایین برنامه من چیه؟
نظرات مطالب
ASP.NET MVC #4
سلام؛ تو کنترلر متدی دارم که ورودی آن int و خروجی string ای را که از دیتابس میگیرد را بر میگرداند. مشکلم اینه که ورودی متد رو باید از view و بر اساس مقداری که کاربر در حین اجرای برنامه تو textbox وارد میکنه بگیرم و خروجی متد را در view به کاربر نشون بدم. چیکار باید بکنم؟  البته مقدار عددی یکی از property‌های viewmodel هست. اما رشته نه!
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 8 - فعال سازی ASP.NET MVC
پیشنیازهای بحث (از قسمت 8 به بعد این سری)
اگر پیشتر سابقه‌ی کار کردن با ASP.NET MVC را ندارید، نیاز است «15 مورد» ابتدایی مطالب ASP.NET MVC سایت را پیش از ادامه‌ی این سری مطالعه کنید؛ از این جهت که این سری از مطالب «ارتقاء» نام دارند و نه «بازنویسی مجدد». دراینجا بیشتر تفاوت‌ها و روش‌های تبدیل کدهای قدیمی، به جدید را بررسی خواهیم کرد؛ تا اینکه بخواهیم تمام مطالبی را که وجود دارند از صفر بازنویسی کنیم.


فعال سازی ASP.NET MVC

تا اینجا خروجی برنامه را صرفا توسط میان افزار app.Run نمایش دادیم. اما در نهایت می‌خواهیم یک برنامه‌ی ASP.NET MVC را برفراز ASP.NET Core 1.0 اجرا کنیم و این قابلیت نیز به صورت پیش فرض غیرفعال است. برای فعال سازی آن نیاز است ابتدا بسته‌ی نیوگت آن‌را نصب کرد. سپس سرویس‌های مرتبط با آن‌را ثبت و معرفی نمود و در آخر میان افزار خاص آن‌را فعال کرد.


نصب وابستگی‌های ASP.NET MVC

برای این منظور بر روی گره references کلیک راست کرده و گزینه‌ی manage nuget packages را انتخاب کنید. سپس در برگه‌ی browse آن Microsoft.AspNetCore.Mvc را جستجو کرده و نصب نمائید:


انجام این مراحل معادل هستند با افزودن یک سطر ذیل به فایل project.json برنامه:
 {
    "dependencies": {
      //same as before  
      "Microsoft.AspNetCore.Mvc": "1.0.0"
 },


تنظیم سرویس‌ها و میان افزارهای ASP.NET MVC

پس از نصب بسته‌ی نیوگت ASP.NET MVC، دو تنظیم ذیل در فایل آغازین برنامه، برای شروع به کار با ASP.NET MVC کفایت می‌کنند:
الف) ثبت یکجای سرویس‌های ASP.NET MVC
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

ب) معرفی میان افزار ASP.NET MVC
public void Configure(IApplicationBuilder app)
{
   app.UseFileServer();
   app.UseMvcWithDefaultRoute();
در مورد متد UseFileServer در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 4 - فعال سازی پردازش فایل‌های استاتیک» بیشتر بحث شد.
در اینجا دو متد UseMvc و UseMvcWithDefaultRoute را داریم. اولی، امکان تعریف مسیریابی‌های سفارشی را میسر می‌کند و دومی به همراه یک مسیریابی پیش فرض است.


افزودن اولین کنترلر برنامه و معرفی POCO Controllers


در ویژوال استودیو بر روی نام پروژه کلیک راست کرده و پوشه‌ی جدیدی را به نام کنترلر اضافه کنید (تصویر فوق). سپس به این پوشه کلاس جدید HomeController را با این محتوا اضافه کنید:
namespace Core1RtmEmptyTest.Controllers
{
    public class HomeController
    {
        public string Index()
        {
            return "Running a POCO controller!";
        }
    }
}
در ادامه برای اینکه فایل index.html موجود در پوشه‌ی wwwroot بجای محتوای اکشن متد Index ما نمایش داده نشود (با توجه به تقدم و تاخر میان افزارهای ثبت شده‌ی در کلاس آغازین برنامه)، این فایل را حذف کره و یا تغییر نام دهید.
سپس برنامه را اجرا کنید. این خروجی باید قابل مشاهده باشد:


اگر با نگارش‌های قبلی ASP.NET MVC کار کرده باشید، تفاوت این کنترلر با آن‌ها، در عدم ارث بری آن از کلاس پایه‌ی Controller است. به همین جهت به آن POCO Controller نیز می‌گویند (plain old C#/CLR object).
در ASP.NET Core، همینقدر که یک کلاس public غیر abstract را که نامش به Controller ختم شود، داشته باشید و این کلاس در اسمبلی باشد که ارجاعی را به وابستگی‌های ASP.NET MVC داشته باشد، به عنوان یک کنترلر معتبر شناخته شده و مورد استفاده قرار خواهد گرفت. در نگارش‌های قبلی، شرط ارث بری از کلاس پایه Controller نیز الزامی بود؛ اما در اینجا خیر. هدف از آن نیز کاهش سربارهای وهله سازی یک کنترلر است. اگر صرفا می‌خواهید یک شیء را به صورت JSON بازگشت دهید، شاید وهله سازی یک کلاس ساده، بسیار بسیار سریعتر از نمونه سازی یک کلاس مشتق شده‌ی از Controller، به همراه تمام وابستگی‌های آن باشد.

 البته هنوز هم مانند قبل، کنترلرهای مشتق شده‌ی از کلاس پایه‌ی Controller قابل تعریف هستند:
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class AboutController : Controller
    {
        public IActionResult Index()
        {
            return Content("Hello from DNT!");
        }
    }
}
با این خروجی:


تفاوت دیگری را که ملاحظه می‌کنید، خروجی IActionResult بجای ActionResult نگارش‌های قبلی است. در اینجا هنوز هم ActionResult را می‌توان بکار برد و اینبار ActionResult، پیاده سازی پیش فرض اینترفیس IActionResult است.
و اگر بخواهیم در POCO Controllers شبیه به return Content فوق را پیاده سازی کنیم، نیاز است تا تمام جزئیات را از ابتدا پیاده سازی کنیم (چون کلاس پایه و ساده ساز Controller در اختیار ما نیست):
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
 
namespace Core1RtmEmptyTest.Controllers
{
    public class HomeController
    {
        [ActionContext]
        public ActionContext ActionContext { get; set; }
 
        public HttpContext HttpContext => ActionContext.HttpContext;
 
        public string Index()
        {
            return "Running a POCO controller!";
        }
 
        public IActionResult About()
        {
            return new ContentResult
            {
                Content = "Hello from DNT!",
                ContentType = "text/plain; charset=utf-8"
            };
        }
    }
}
همانطور که ملاحظه می‌کنید اینبار بجای return Content ساده، باید وهله سازی شیء ContentResult از ابتدا صورت گیرد؛ به همراه تمام جزئیات آن.
به علاوه در اینجا نحوه‌ی دسترسی به HttpContext را هم مشاهده می‌کنید. ویژگی ActionContext سبب تزریق اطلاعات آن به کنترلر جاری شده و سپس از طریق آن می‌توان به HttpContext و تمام قابلیت‌های آن دسترسی یافت.
اینجا است که می‌توان میزان سبکی و سریعتر بودن POCO Controllers را احساس کرد. شاید در کنترلری نیاز به این وابستگی‌ها نداشته باشید. اما زمانیکه کنترلری از کلاس پایه‌ی Controller مشتق می‌شود، تمام این وابستگی‌ها را به صورت پیش فرض و حتی در صورت عدم استفاده، در اختیار خواهد داشت و این در اختیار داشتن یعنی وهله سازی شدن تمام وابستگی‌های مرتبط با شیء پایه‌ی Controller. به همین جهت است که POCO Controllers بسیار سبک‌تر و سریع‌تر از کنترلرهای متداول مشتق شده‌ی از کلاس پایه‌ی Controller عمل می‌کنند.
مطالب
وی‍‍ژگی های پیشرفته ی AutoMapper - قسمت اول
در پست قبلی مقدمه‌ای داشتیم بر AutoMapper؛ مثالی که در اون پست عنوان شد ساده‌ترین و پرکاربردترین روش استفاده از AutoMapper هست بنام Flattening که در واقع از یک شیء کل به یک شیء کوچکتر می‌رسیم.
همانطور که در قسمت اول گفتم AutoMapper کارش رو بر اساس قراردادها انجام میده یا همون Convention Base. یکی از قردادهای AutoMapper، نگاشت براساس نام اعضای اون شی هست؛ مثلا در مثال قبلی FirstName در مبداء، به خاصیتی با همین نام نگاشت شد و ...

Projection
روش استفاده بعدی که به اون Projection (معنی فارسی خوب برا Projection چیه ؟) میگن برای مواقعی هست که اعضای یک شیء در مبداء، همتایی در مقصد ندارد (از نظر نام) و در واقع می‌خواهیم یک شیء رو به یک شیء دیگه تغییر شکل بدیم.
مدل زیر رو در نظر بگیرید:
  public class Person
    {
        public int Id { get; set; }

        public string FirstName { get; set; }       

        public string LastName { get; set; }

    }

  که قرار است به مدل PersonDTO نگاشت بشه.
  public class PersonDTO
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public string Family { get; set; }

public string FullName { get; set; }

        public string Email { get; set; }
    }
همنطور که می‌بینید در مقصد خاصیت‌هایی داریم که در مبداء همتایی ندارند. برای نگاشت چنین اشیایی، از متد ForMember  استفاده و نگاشت‌های شی‌های موردنظر رو Custom می‌کنیم.
 Mapper.CreateMap<Person, PersonDTO>().ForMember(des => des.Name, op => op.MapFrom(src => src.FirstName)).
                ForMember(des => des.FullName, op => op.MapFrom(src => src.FirstName + " " + src.LastName)).ForMember(
                    des => des.Email, op => op.Ignore());
متد ForMember  از یک Action Delegate  برای کانفیگ کردن هر عضو استفاده میکنه. در مثال بالا ما از MapFrom برای اعمال نگاشت Custom  استفاده کردیم.
AutoMapper.IMappingExpression<TSource,TDestination>.ForMember(System.Linq.Expressions.Expression<System.Func<TDestination,object>>, System.Action<AutoMapper.IMemberConfigurationExpression<TSource>>)

نکته:برای تست کردن اینکه آیا نگاشت ما Exception  داره یا نه میتوان از متد زیر استفاده کرد.
Mapper.AssertConfigurationIsValid();

نکته
: همیشه موقع نگاشت، اعضای شیء مقصد برای AutoMapper  مهمن؛ مثلا در مثال بالا خاصیت LastName در مبداء به هیچ عضوی در مقصد نگاشت نشده (به صورت مستقیم) و این تولید Exception  نمیکنه ولی برعکس اون باعث تولید Exception میشه؛ مثلا اگه خاصیت Email  رو که در مبداء همتایی نداره رو کانفیگ نکنیم، باعث تولید Exception میشه.
 
نحوه استفاده
var person = new Person
                {
                    Id = 1,
                    FirstName = "Mohammad",
                    LastName = "Saheb",
                    
                };
var personDTO = Mapper.Map<Person, PersonDTO>(person);
خروجی به شکل زیر میشه


Collection
بعد از نوشتن کانفیگ نگاشت‌ها، بدون نیاز به تنظیمات خاصی میتونیم مجموعه ای از شی‌های مبداء رو به مقصد نگاشت کنیم. مجموعه‌های پشتیبانی شده به شرح زیرن.
IEnumerable, IEnumerable<T>, ICollection, ICollection<T>, IList, IList<T>, List<T>.
و البته آرایه ها.
مثال
var persons = new[]
                {
                    new Person { Id = 1, FirstName = "Mohammad", LastName = "Saheb" },
                    new Person { Id = 2, FirstName = "Babak", LastName = "Saheb" }
                };

var personDTOs = Mapper.Map<Person[], List<PersonDTO>>(persons);
خروجی به شکل زیر میشه

Nested Mappings
برای نگاشت کلاس‌های تو در تو از این روش استفاده می‌کنیم و ...
فرض کنید در کلاس Person خاصیتی از نوع Address داریم و در کلاس PersonDTO  خاصیتی از نوع AddressDTO.
public class Address
    {
        public string Ad1 { get; set; }
        public string Ad2 { get; set; }
    }


public class AddressDTO
    {
        public string Ad1 { get; set; }
        public string Ad2 { get; set; }
    }
برای نگاشت‌هایی از این دست باید تنظیمات نگاشت مربوط به نوع‌های تودرتو را صریحا معین کنیم.
Mapper.CreateMap<Address, AddressDTO>();
نکته: هرچند منطقی‌تر بنظر میرسه که تعریف نگاشت‌های داخلی ابتدا بیاد، ولی فرقی نمیکنه که تعریف این نگاشت قبل یا بعد از نگاشت اصلی باشه.
ادامه دارد...
مطالب
مایکرو سرویس‌ها - قسمت 1 - معرفی
در نرم افزار‌های Enterprise، توسعه محصول، چالش اصلی تیم نمی‌باشد. اصلی‌ترین چالش، بعد از استقرار نرم افزار و زیر بار رفتن آن به‌وجود می‌آید؛ مسائلی نظیر مدیریت تغییرات و scaling و چنانچه نرم افزار بصورت صحیحی توسعه نیافته باشد، می‌توان گفت که انجام موارد ذکر شده بسیار سخت یا شاید غیر ممکن شوند و باید نرم افزار، بازنویسی شود.
برای روشن شدن موضوع یک مثال میزنم.
فرض کنید یک نرم افزار جامع بیمه (Core Insurance) داریم که بصورت یک نرم افزار یکپارچه (Monolithic) ارائه شده است. بعد از چند سال قرار است در زیر سیستم‌های مختلف تغییراتی انجام شود؛ مثلا زیر سیستم‌های بیمه عمر، بیمه مسافرتی و بیمه درمان، نیاز به تغییر دارند. فرض کنید تغییرات در بیمه درمان سریعتر انجام شده و آماده استفاده برای مشتری می‌باشد؛ اما به دلیل یکپارچه بودن سیستم، این انتشار نسخه باید تا اتمام کار زیر سیستم‌های دیگر، به تعویق بیفتد. یا اینکه به دلیل بالا رفتن تعداد کاربران می‌خواهیم سیستم را  scale out کنیم. برای این منظور باید چند نسخه از کل سیستم را در پروسه‌های مجزایی قرار دهیم.
با توجه به توضیحات بالا متوجه این منظور میشویم که مدیریت تغییرات، بخاطر وابستگی‌های بیش از حد سیستم، با کندی روبه رو می‌شود و همچنین هزینه Scale سیستم با توجه به اینکه باید کل سیستم را  در پروسه‌های مختلفی نصب کرد، بالا می‌باشد.
اگر این سیستم یکپارچه به زیر سیستم‌های مجزایی شکسته می‌شد، هزینه تغییرات و Scale آن به مراتب کمتر می‌شد. حتی از این جلوتر بریم و هر کدام از این زیر سیستم‌ها قابلیت‌های کسب و کار (Business Capabilities) خودشان را به‌صورت سرویس‌های مجزایی ارائه دهند، هزینه تغییرات و نگهداری آن‌ها چگونه خواهد بود؟!
برای مثال اگر زیر سیستم بیمه عمر را تصور و آن‌را به سه قسمت درخواست بیمه نامه ، صدور بیمه نامه و بخش خسارت تقسیم کنیم که هر کدام از این قسمت‌ها به تنهایی قابل ارائه به مشتری باشند.
برای مثال درخواست بیمه نامه شامل پر کردن فرم پیشنهاد، بررسی اطلاعات وارد شده توسط پزشکان بیمه و اعلام نظر کار شناسان بیمه برای افزایش نرخ بیمه نامه بر اساس ریسک‌های پزشکی و شغلی بیمه شده می‌باشد که در نهایت بعد از چند روز، یک فرم پیشنهاد به تایید کارشناسا ن رسیده و تازه به بیمه نامه تبدیل می‌شود. همانطور که می‌بینید این بخش به تنهایی می‌تواند در اختیار نمایندگی‌های شرکت بیمه قرار گرفته و قسمت اولیه فروش بیمه نامه را پشتیبانی کند. حالا اگر نیاز به تغییرات یا scaling سیستم وجود داشته باشد، انجام دادن آن‌ها به مراتب راحت‌تر و کم هزینه‌تر می‌باشد.

مایکرو سرویس چیست ؟

در یک تعریف کوتاه، در معماری مایکرو سرویس، توسعه یک نرم افزار به‌صورت مجموعه‌ای از سرویسهای کوچک می‌باشد که این سرویسها به‌صورت کاملا مستقلی قابلیت استقرار دارند و هر کدام از این سرویسها می‌توانند توسط تیم‌های جداگانه‌ای با پلتفرم توسعه و زبان برنامه نویسی و بانک اطلاعاتی جداگانه‌ای توسعه داده شوند  و با یک مکانیزم سبک  وزن مانند Http با یکدیگر در ارتباط باشند.

این روش پیاده سازی قابلیت مقیاس پذیری و تست پذیری را بالا میبرد و توسعه و نگهداری سیستم را آسان می‌کند. دلیل آن هم کاملا مشخص است؛ هر سرویس یک وظیفه مشخص دارد و تیم توسعه‌ی آن کاملا بر آن مسلط می‌باشد و با توجه به اینکه این سرویسها خیلی بزرگ نیستند، تغییرات و تست و نگهداری آن آسان میشود .

چرا معماری مایکرو سرویس؟

مایکرو سرویس‌ها به شما قابلیت چابکی بیشتری می‌دهند و شما را قادر میسازند تا به‌صورت بهتری بتوانید یک سیستم بزرگ، پیچیده و در مقیاس بزرگ را نگهداری کنید.
این سرویس‌ها به تنهایی قابلیت scaling دارند و برخلاف یک سیستم یکپارچه که برای scaling باید تمام سیستم را به عنوان یک واحد scale out کرد، در مایکرو سرویس‌ها شما می‌توانید سرویس‌هایی را که بیشتر مورد استفاده قرار می‌گیرند، بصورت کاملا مستقلی scale out کنید و به این صورت چابکی شما در مواجه با تغییرات که از خصوصیات اصلی یک سیستم نرم افزاری می‌باشد، بالا می‌رود. 
با توجه به توضیحات بالا تصویر زیر گویای همه‌ی این موارد هست:

مقایسه سیستم یکپارچه با مایکروسرویسدر مطالب بعدی در موردی مشخصه‌های مایکرو سرویس‌ها صحبت خواهیم کرد.

نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت پنجم - بررسی چرخه‌ی حیات کامپوننت‌ها
با تشکر از مطلب مفیدتون .
چطوری میشه به این شکل نوشت؟
 @Component({ selector: 'pm-bank', templateUrl: '../public/pages/public.accounting.banks.html'
})
@Pipe({ name: 'productFilter' })
در زمان نوشتن این قسمت ارور Decorators are not valid here. 
نظرات اشتراک‌ها
راهنمای مایکروسافت در مورد مقابله با SQL Injection
با سلام؛

من یک Function برای جلوگیری از  SQL Injection  برای خودم نوشتم می‌خواستم بدونم چند درصد میتونه کارساز باشه ؟ هر متنی که از کاربر گرفته میشه اول وارد این Function  میشه و پاک سازی میشه
ممنون میشم نظرتون رو بفرمائید
   Public Function sqlinj(ByVal text As String) As String
        text = Strings.Replace(text, "'", "")
        text = Strings.Replace(text, ",", "")
        text = Strings.Replace(text, "+", "")
        text = Strings.Replace(text, "&", "")
        text = Strings.Replace(text, "=", "")
        text = Strings.Replace(text, "%", "")
        text = Strings.Replace(text, ":", "")
        text = Strings.Replace(text, ";", "")
        text = Strings.Replace(text, "select", "")
        text = Strings.Replace(text, "delete", "")
        text = Strings.Replace(text, "insert", "")
        text = Strings.Replace(text, "update", "")
        text = Strings.Replace(text, "where", "")
        text = Strings.Replace(text, "union", "")
        text = Strings.Replace(text, "alter", "")
        text = Strings.Replace(text, "drop", "")
        text = Strings.Replace(text, "%", "")
        text = Strings.Replace(text, "_", "")
        Return text
    End Function

مطالب
React 16x - قسمت 27 - احراز هویت و اعتبارسنجی کاربران - بخش 2 - استخراج و نمایش اطلاعات JWT و خروج از سیستم
در قسمت قبل، در هر دو حالت ثبت نام یک کاربر جدید و همچنین ورود به سیستم، یک JSON Web Token را از سرور دریافت کرده و در local storage مرورگر، ذخیره کردیم. اکنون قصد داریم محتوای این توکن را استخراج کرده و از آن جهت نمایش اطلاعات کاربر وارد شده‌ی به سیستم، استفاده کنیم. همچنین کار بهبود کیفیت کدهایی را هم که تاکنون پیاده سازی کردیم، انجام خواهیم داد.


نگاهی به محتوای JSON Web Token تولیدی

اگر مطلب قسمت قبل را پیگیری کرده باشید، پس از لاگین، یک چنین خروجی را در کنسول توسعه دهندگان مرورگر می‌توان مشاهده کرد که همان return Ok(new { access_token = jwt }) دریافتی از سمت سرور است:


اکنون این رشته‌ی طولانی را در حافظه کپی کرده و سپس به سایت https://jwt.io/#debugger-io مراجعه و در قسمت دیباگر آن، این رشته‌ی طولانی را paste می‌کنیم تا آن‌را decode کند:


برای نمونه payload آن حاوی یک چنین اطلاعاتی است:
{
  "jti": "b2921057-32a4-fbb2-0c18-5889c1ab8e70",
  "iss": "https://localhost:5001/",
  "iat": 1576402824,
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "1",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Vahid N.",
  "DisplayName": "Vahid N.",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata": "1",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin",
  "nbf": 1576402824,
  "exp": 1576402944,
  "aud": "Any"
}
در اینجا یک‌سری از اطلاعات کاربر، مانند id ، name ، DisplayName و یا حتی role او درج شده‌است؛ به همراه تاریخ صدور (iat) و انقضای (exp) این token که به صورت Unix time format بیان می‌شوند. به هر کدام از این خواصی که در اینجا ذکر شده‌اند، یک user claim گفته می‌شود. به عبارتی، این token ادعا می‌کند (claims) که نقش کاربر وارد شده‌ی به سیستم، Admin است. برای بررسی صحت این ادعا نیز یک امضای دیجیتال (مشخص شده‌ی با رنگ آبی) را به همراه این توکن سه قسمتی (قسمت‌های مختلف آن، با 2 نقطه از هم جدا شده‌اند که در تصویر نیز با سه رنگ متمایز، مشخص است)، ارائه کرده‌است. به این معنا که اگر قسمتی از اطلاعات این توکن، در سمت کاربر دستکاری شود، دیگر در سمت سرور تعیین اعتبار مجدد نخواهد شد؛ چون نیاز به یک امضای دیجیتال جدید را دارد که کلیدهای خصوصی تولید آن، تنها در سمت سرور مهیا هستند و به سمت کلاینت ارسال نمی‌شوند.


استخراج اطلاعات کاربر وارد شده‌ی به سیستم، از JSON Web Token دریافتی

همانطور که در payload توکن دریافتی از سرور نیز مشخص است، اطلاعات ارزشمندی از کاربر، به همراه آن ارائه شده‌اند و مزیت کار با آن، عدم نیاز به کوئری گرفتن مداوم از سرور و بانک اطلاعاتی، جهت دریافت مجدد این اطلاعات است. بنابراین اکنون در برنامه‌ی React خود، قصد داریم مشابه کاری را که سایت jwt.io انجام می‌دهد، پیاده سازی کرده و به این اطلاعات دسترسی پیدا کنیم و برای مثال DisplayName را در Navbar نمایش دهیم. برای این منظور فایل app.js را گشوده و تغییرات زیر را به آن اعمال می‌کنیم:
- می‌خواهیم اطلاعات کاربر جاری را در state کامپوننت مرکزی App قرار دهیم. سپس زمانیکه کار رندر کامپوننت NavBar درج شده‌ی در متد رندر آن فرا می‌رسد، می‌توان این اطلاعات کاربر را به صورت props به آن ارسال کرد؛ و یا به هر کامپوننت دیگری در component tree برنامه.
- بنابراین ابتدا کامپوننت تابعی بدون حالت App را تبدیل به یک کلاس کامپوننت استاندارد مشتق شده‌ی از کلاس پایه‌ی Component می‌کنیم. اکنون می‌توان state را نیز به آن اضافه کرد:
class App extends Component {
  state = {};
- سپس متد componentDidMount را به این کامپوننت اضافه می‌کنیم؛ در آن ابتدا token ذخیره شده‌ی در local storage را دریافت کرده و سپس decode می‌کنیم تا payload اطلاعات کاربر وارد شده‌ی به سیستم را استخراج کنیم. در آخر state را توسط این اطلاعات به روز می‌کنیم.
- برای decode کردن توکن، نیاز به نصب کتابخانه‌ی زیر را داریم:
> npm install --save jwt-decode
- پس از نصب آن، ابتدا امکانات آن‌را import کرده و سپس از آن در متد componentDidMount استفاده می‌کنیم:
import jwtDecode from "jwt-decode";
// ...

class App extends Component {
  state = {};

  componentDidMount() {
    try {
      const jwt = localStorage.getItem("token");
      const currentUser = jwtDecode(jwt);
      console.log("currentUser", currentUser);
      this.setState({ currentUser });
    } catch (ex) {
      console.log(ex);
    }
  }
ابتدا آیتمی با کلید token از localStorage استخراج می‌شود. سپس توسط متد jwtDecode، تبدیل به یک شیء حاوی اطلاعات کاربر جاری وارد شده‌ی به سیستم گشته و در آخر در state درج می‌شود. در اینجا درج try/catch ضروری است؛ از این جهت که متد jwtDecode، در صورت برخورد به توکنی غیرمعتبر، یک استثناء را صادر می‌کند و این استثناء نباید بارگذاری برنامه را با اخلال مواجه کند. از این جهت که اگر توکنی غیرمعتبر است (و یا حتی در localStorage وجود خارجی ندارد؛ برای کاربران لاگین نشده)، کاربر باید مجددا برای دریافت نمونه‌ی معتبر آن، لاگین کند.

- اکنون می‌توان شیء currentUser را به صورت props، به کامپوننت NavBar ارسال کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />
        <NavBar user={this.state.currentUser} />
        <main className="container">


نمایش اطلاعات کاربر وارد شده‌ی به سیستم در NavBar

پس از ارسال شیء کاربر به صورت props به کامپوننت src\components\navBar.jsx، کدهای این کامپوننت را به صورت زیر جهت نمایش نام کاربر جاری وارد شده‌ی به سیستم تغییر می‌دهیم:
const NavBar = ({ user }) => {
چون این کامپوننت به صورت یک کامپوننت تابعی بدون حالت تعریف شده، برای دریافت props می‌توان یا آن‌را به صورت مستقیم به عنوان پارامتر تعریف کرد و یا خواص مدنظر را با استفاده از Object Destructuring به عنوان پارامتر دریافت نمود.
سپس می‌توان لینک‌های Login و Register را به صورت شرطی رندر کرد و نمایش داد:
{!user && (
  <React.Fragment>
    <NavLink className="nav-item nav-link" to="/login">
      Login
    </NavLink>
    <NavLink className="nav-item nav-link" to="/register">
      Register
    </NavLink>
  </React.Fragment>
)}
در اینجا اگر شیء user تعریف شده باشد (یعنی کاربر، توکن ذخیره شده‌ای در local storage داشته باشد)، دیگر لینک‌های login و register نمایش داده نمی‌شوند. به علاوه برای اعمال && به چند المان React، نیاز است آن‌ها را داخل یک والد، مانند React.Fragment محصور کرد.

شبیه به همین حالت را برای هنگامیکه کاربر، تعریف شده‌است، جهت نمایش نام او و لینک به Logout، نیاز داریم:
{user && (
  <React.Fragment>
    <NavLink className="nav-item nav-link" to="/logout">
      Logout
    </NavLink>
    <NavLink className="nav-item nav-link" to="/profile">
      {user.DisplayName}
    </NavLink>
  </React.Fragment>
)}
user.DisplayName درج شده‌ی در اینجا، اطلاعات خودش را از payload توکن decode شده‌ی دریافتی از سرور، تامین می‌کند؛ با این خروجی:


فعلا تا پیش از پیاده سازی Logout، برای آزمایش آن، به کنسول توسعه دهندگان مرورگر مراجعه کرده و توکن ذخیره شده‌ی در ذیل قسمت application->storage را دستی حذف کنید. سپس صفحه را ریفرش کنید. اینبار لینک‌های به Login و Register نمایان می‌شوند.
یک مشکل! در این حالت (زمانیکه توکن حذف شده‌است)، از طریق قسمت Login به برنامه وارد شوید. هرچند این قسمت‌ها به درستی کار خود را انجام می‌دهند، اما هنوز در منوی بالای سایت، نام کاربری و لینک به Logout ظاهر نشده‌اند. علت اینجا است که در کامپوننت App، کار دریافت توکن در متد componentDidMount انجام می‌شود و این متد نیز تنها یکبار در طول عمر برنامه فراخوانی می‌شود. برای رفع این مشکل به src\components\loginForm.jsx مراجعه کرده و بجای استفاده از history.push برای هدایت کاربر به صفحه‌ی اصلی برنامه، نیاز خواهیم داشت تا کل برنامه را بارگذاری مجدد کنیم. یعنی بجای:
this.props.history.push("/");
باید از سطر زیر استفاده کرد:
window.location = "/";
این سطر سبب full page reload برنامه شده و در نتیجه متد componentDidMount کامپوننت App، یکبار دیگر فراخوانی خواهد شد. شبیه به همین کار را در کامپوننت src\components\registerForm.jsx نیز باید انجام داد.


پیاده سازی Logout کاربر وارد شده‌ی به سیستم

برای logout کاربر تنها کافی است توکن او را از local storage حذف کنیم. به همین جهت مسیریابی جدید logout را که به صورت لینکی به NavBar اضافه کردیم:
<NavLink className="nav-item nav-link" to="/logout">
   Logout
</NavLink>
به فایل src\App.js اضافه می‌کنیم.
import Logout from "./components/logout";
// ...

class App extends Component {
  render() {
    return (
          // ...
          <Switch>
            // ...
            <Route path="/logout" component={Logout} />
البته برای اینکار نیاز است کامپوننت جدید src\components\logout.jsx را با محتوای زیر ایجاد کنیم:
import { Component } from "react";

class Logout extends Component {
  componentDidMount() {
    localStorage.removeItem("token");
    window.location = "/";
  }

  render() {
    return null;
  }
}

export default Logout;
که در متد componentDidMount آن، کار حذف توکن ذخیره شده‌ی در localStorage انجام شده و سپس کاربر را با یک full page reload، به ریشه‌ی سایت هدایت می‌کنیم.


بهبود کیفیت کدهای نوشته شده

اگر به کامپوننت App دقت کنید، کلید token استفاده شده‌ی در آن، در چندین قسمت برنامه مانند login و logout، تکرار و پراکنده شده‌است. بنابراین بهتر است جزئیات پیاده سازی مرتبط با اعتبارسنجی کاربران، به ماژول مختص به آن‌ها (src\services\authService.js) منتقل شود تا سایر قسمت‌های برنامه، به صورت یک‌دستی از آن استفاده کنند و اگر در این بین نیاز به تغییری بود، فقط یک ماژول نیاز به تغییر، داشته باشد.
برای این منظور، ابتدا متد login قبلی را طوری تغییر می‌دهیم که کار ذخیره سازی توکن را نیز در authService.js انجام دهد:
const tokenKey = "token";

export async function login(email, password) {
  const {
    data: { access_token }
  } = await http.post(apiEndpoint + "/login", { email, password });
  console.log("JWT", access_token);
  localStorage.setItem(tokenKey, access_token);
}
سپس متد doSumbit کامپوننت src\components\loginForm.jsx، به صورت زیر ساده می‌شود:
const { data } = this.state;
await auth.login(data.username, data.password);
window.location = "/";

همین‌کار را برای logout نیز در authService انجام داده:
export function logout() {
  localStorage.removeItem(tokenKey);
}
و در ادامه متد componentDidMount کامپوننت Logout را برای استفاده‌ی از آن، اصلاح می‌کنیم:
import * as auth from "../services/authService";

class Logout extends Component {
  componentDidMount() {
    auth.logout();

منطق دریافت اطلاعات کاربر جاری نیز باید به authService منتقل شود؛ چون مسئولیت دریافت توکن و سپس decode آن، نباید به کامپوننت App واگذار شود:
import jwtDecode from "jwt-decode";
//...

export function getCurrentUser() {
  try {
    const jwt = localStorage.getItem(tokenKey);
    const currentUser = jwtDecode(jwt);
    console.log("currentUser", currentUser);
    return currentUser;
  } catch (ex) {
    console.log(ex);
    return null;
  }
}
سپس متد componentDidMount کامپوننت App، به صورت زیر خلاصه خواهد شد:
import * as auth from "./services/authService";

class App extends Component {
  state = {};

  componentDidMount() {
    const currentUser = auth.getCurrentUser();
    this.setState({ currentUser });
  }

جای دیگری که از localStorage استفاده شده، متد doSumbit کامپوننت ثبت نام کاربران است. این قسمت را نیز به صورت زیر به authService اضافه می‌کنیم:
export function loginWithJwt(jwt) {
   localStorage.setItem(tokenKey, jwt);
}
سپس ابتدای متد doSumbit را برای استفاده‌ی از آن به صورت زیر تغییر می‌دهیم:
import * as auth from "../services/authService";
// ...
const response = await userService.register(this.state.data);
auth.loginWithJwt(response.headers["x-auth-token"]);


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-27-backend.zip و sample-27-frontend.zip