پاسخ به بازخورد‌های پروژه‌ها
'System.Data.Entity.Core.EntityException'
در بازخورد‌های قبلی همچین مشکلی گزارش شده بود و جواب داده شده است.
در این فایل  ، خط ۳۴ را کامنت کرده و خطوط ۳۵و۳۶ را unComment کنید.
مطالب
پیاده‌سازی الگوی Transaction Per Request در EF
قبلاً در سایت جاری در رابطه با پیاده‌سازی الگوی Context Per Request مطالبی منتشر شده است. در ادامه می‌خواهیم تمامی درخواست‌های خود را اتمیک کنیم. همانطور که قبلاً در این مطلب مطالعه کردید یکی از مزایای الگوی Context Per Request، استفاده‌ی صحیح از تراکنش‌ها می‌باشد. به عنوان مثال اگر در حین فراخوانی متد SaveChanges، خطایی رخ دهد، کلیه‌ی عملیات RollBack خواهد شد. اما حالت زیر را در نظر بگیرید:
_categoryService.AddNewCategory(category);
_uow.SaveAllChanges();

throw new InvalidOperationException();

return RedirectToAction("Index");
همانطور که در کدهای فوق مشاهده می‌کنید، قبل از ریدایرکت شدن صفحه، یک استثناء را صادر کرده‌ایم. در این حالت، تغییرات درون دیتابیس ذخیره می‌شوند! یعنی حتی اگر یک استثناء نیز در طول درخواست رخ دهد، قسمتی از درخواست که در اینجا ذخیره‌سازی گروه محصولات است، درون دیتایس ذخیره خواهد شد؛ در نتیجه درخواست ما اتمیک نیست.
برای رفع این مشکل می‌توانیم یکسری وظایف (Tasks) را تعریف کنیم که در نقاط مختلف چرخه‌ی حیات برنامه اجرا شوند. هر کدام از این وظایف تنها کاری که انجام می‌دهند فراخوانی متد Execute خودشان است. در ادامه می‌خواهیم از این وظایف جهت پیاده‌سازی الگوی Transaction Per Request استفاده کنیم. در نتیجه اینترفیس‌های زیر را ایجاد خواهیم کرد:
public interface IRunAtInit
{
       void Execute();
}
public interface IRunAfterEachRequest
{
       void Execute(); 
}
public interface IRunAtStartUp
{
       void Execute(); 
}
public interface IRunOnEachRequest
{
       void Execute(); 
}
public interface IRunOnError
{
       void Execute(); 
}
خوب، این اینترفیس‌ها همانطور که از نامشان پیداست، همان اعمال را پیاده سازی خواهند کرد:
IRunAtInit: اجرای وظایف در زمان بارگذاری اولیه‌ی برنامه.
IRunAfterEachRequest: اجرای وظایف بعد از اینکه درخواستی فراخوانی (ارسال) شد.
IRunAtStartUp: اجرای وظایف در زمان StartUp برنامه.
IRunOnEachRequest: اجرای وظایف در ابتدای هر درخواست.
IRunOnError: اجرای وظایف در زمان بروز خطا یا استثناء‌های مدیریت نشده‌ی برنامه.
خوب، یک کلاس می‌تواند با پیاده‌سازی هر کدام از اینترفیس‌های فوق تبدیل به یک task شود. همچنین از این جهت که اینترفیس‌های ما ساده هستند و هر اینترفیس یک متد Execute دارد، عملکرد آن‌ها تنها اجرای یکسری دستورات در حالات مختلف می‌باشد.
قدم بعدی افزودن قابلیت پشتیبانی از این وظایف در برنامه‌مان است. اینکار را با پیاده‌سازی ریجستری زیر انجام خواهیم داد:
public class TaskRegistry : StructureMap.Configuration.DSL.Registry
{
        public TaskRegistry()
        {
            Scan(scan =>
            {
                scan.Assembliy("yourAssemblyName");
                scan.AddAllTypesOf<IRunAtInit>();
                scan.AddAllTypesOf<IRunAtStartUp>();
                scan.AddAllTypesOf<IRunOnEachRequest>();
                scan.AddAllTypesOf<IRunOnError>();
                scan.AddAllTypesOf<IRunAfterEachRequest>();
            });
        }
}
با این کار استراکچرمپ اسمبلی معرفی شده را بررسی کرده و هر کلاسی که اینترفیس‌های ذکر شده را پیاده‌سازی کرده باشد، رجیستر می‌کند. قدم بعدی افزودن رجیستری فوق و بارگذاری آن درون کانتینرمان است:
ioc.AddRegistry(new TaskRegistry());
اکنون وظایف درون کانتینرمان بارگذاری شده‌اند. سپس نوبت به استفاده‌ی از این وظایف است. 
خوب، باید درون فایل Global.asax کدهای زیر را قرار دهیم. چون همانطور که عنوان شد وظایف ایجاد شده می‌بایستی در نقاط مختلف برنامه اجرا شوند:
protected void Application_Start()
{
   // other code
   foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAtInit>())
   {
                task.Execute();
    }
}
protected void Application_BeginRequest()
 {
           foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnEachRequest>())
           {
                task.Execute();
           }
}
protected void Application_EndRequest(object sender, EventArgs e)
{
            try
            {
                foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunAfterEachRequest>())
                {
                    task.Execute();
                }
            }
            finally
            {
                HttpContextLifecycle.DisposeAndClearAll();
                MiniProfiler.Stop();
            }
}
protected void Application_Error()
{
            foreach (var task in SmObjectFactory.Container.GetAllInstances<IRunOnError>())
            {
                task.Execute();
            }
}
همانطور که مشاهده می‌کنید، هر task در قسمت خاص خود فراخوانی خواهد شد. مثلاً IRunOnError درون رویداد Application_Error و دیگر وظایف نیز به همین ترتیب.
اکنون برنامه به صورت کامل از وظایف پشتیبانی می‌کند. در ادامه، کلاس زیر را ایجاد خواهیم کرد. این کلاس چندین اینترفیس را از اینترفیس‌های ذکر شده، پیاده‌سازی می‌کند:
public class TransactionPerRequest : IRunOnEachRequest, IRunOnError, IRunAfterEachRequest
{
        private readonly IUnitOfWork _uow;
        private readonly HttpContextBase _httpContext;
        public TransactionPerRequest(IUnitOfWork uow, HttpContextBase httpContext)
        {
            _uow = uow;
            _httpContext = httpContext;
        }


        void IRunOnEachRequest.Execute()
        {
            _httpContext.Items["_Transaction"] =
                _uow.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted);
        }

        void IRunOnError.Execute()
        {
            _httpContext.Items["_Error"] = true;
        }

        void IRunAfterEachRequest.Execute()
        {
            var transaction = (DbContextTransaction) _httpContext.Items["_Transaction"];
            if (_httpContext.Items["_Error"] != null)
            {
                transaction.Rollback();
            }
            else
            {
                transaction.Commit();
            }
        }
}
توضیحات کلاس فوق:
در کلاس TransactionPerRequest به دو وابستگی نیاز خواهیم داشت: IUnitOfWork برای کار با تراکنش‌ها و HttpContextBase برای دریافت درخواست جاری. همانطور که مشاهده می‌کنید در متد IRunOnEachRequest.Execute یک تراکنش را آغاز کرده‌ایم و در IRunAfterEachRequest.Execute یعنی در پایان یک درخواست، تراکنش را commit کرده‌ایم. این مورد را با چک کردن یک فلگ در صورت عدم بروز خطا انجام داده‌ایم. اگر خطایی نیز وجود داشته باشد، کل عملیات roll back خواهد شد. لازم به ذکر است که فلگ خطا نیز درون متد IRunOnError.Execute به true مقداردهی شده است.
خوب، پیاده‌سازی الگوی Transaction Per Request به صورت کامل انجام گرفته است. اکنون اگر برنامه را در حالت زیر اجرا کنید:
_categoryService.AddNewCategory(category);
_uow.SaveAllChanges();

throw new InvalidOperationException();

return RedirectToAction("Index");
خواهید دید که عملیات roll back شده و تغییرات در دیتابیس (در اینجا ذخیره سازی گروه محصولات) اعمال نخواهد شد.
مطالب
Design Pattern: Factory

الگوهای طراحی، سندها و راه حلهای از پیش تعریف شده و تست شده‌ای برای مسائل و مشکلات روزمره‌ی برنامه نویسی می‌باشند که هر روزه ما را درگیر خودشان می‌کنند. هر چقدر مقیاس پروژه وسیعتر و تعداد کلاسها و اشیاء بزرگتر باشند، درگیری برنامه نویس و چالش برای مرتب سازی و خوانایی برنامه و همچنین بالا بردن کارآیی و امنیت افزون‌تر می‌شود. از همین رو استفاده از ساختارهایی تست شده برای سناریوهای یکسان، امری واجب تلقی می‌شود.

الگوهای طراحی از لحاظ سناریو، به سه گروه عمده تقسیم می‌شوند:

1- تکوینی: هر چقدر تعداد کلاسها در یک پروژه زیاد شود، به مراتب تعداد اشیاء ساخته شده از آن نیز افزوده شده و پیچیدگی و درگیری نیز افزایش می‌یابد. راه حل‌هایی از این دست، تمرکز بر روی مرکزیت دادن به کلاسها با استفاده از رابط‌ها و کپسوله نمودن (پنهان سازی) اشیاء دارد. 

2- ساختاری: گاهی در پروژه‌ها پیش می‌آید که می‌خواهیم ارتباط بین دو کلاس را تغییر دهیم. از این رو امکان از هم پاشی اجزایِ دیگر پروژه پیش می‌آید. راه حلهای ساختاری، سعی در حفظ انسجام پروژه در برابر این دست از تغییرات را دارند.

3- رفتاری: گاهی بنا به مصلحت و نیاز مشتری، رفتار یک کلاس می‌بایستی تغییر نماید. مثلا چنانچه کلاسی برای ارائه صورتحساب داریم و در آن میزان مالیات 30% لحاظ شده است، حال این درصد باید به عددی دیگر تغییر کند و یا پایگاه داده به جای مشاهده‌ی تعدادِ معدودی گره از درخت، حال می‌بایست تمام گره‌ها را ارائه نماید.


الگوی فکتوری:

الگوی فکتوری در دستهء اول قرار می‌گیرد. من در اینجا به نمونه‌ای از مشکلاتی که این الگو حل می‌نماید، اشاره می‌کنم:

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


یک راه این است که با کلیک روی دکمه‌ی چاپ، نوع مشتری تشخیص داده شود و به ازای نوع مشتری، یک شیء از کلاس مشخص شده برای همان نوع ساخته شود.

 

 

            // Get Customer Type from Customer click on Print Button
            int customerType = 0;

            // Create Object without instantiation
            object obj;


            //Instantiate obj according to customer Type
            if (customerType == 1)
            {
                obj = new Customer1();
            }
            else if (customerType == 2)
            {
                obj = new Customer2();
            }
            // Problem:
            //          1: Scattered New Keywords
            //          2: Client side is aware of Customer Type

 همانگونه که مشاهده می‌نمایید در این سبک کدنویسی غیرحرفه‌ای، مشکلاتی مشهود است که قابل اغماض نیستند. در ابتدا سمت کلاینت دسترسی مستقیم به کلاسها دارد و همانگونه که در شکل بالا قابل مشاهده است کلاینت مستقیما به کلاس وصل است. مشکل دوم عدم پنهان سازی کلاس از دید مشتری است.

راه حل: این مشکل با استفاده از الگوی فکتوری قابل حل است. با استناد به الگوی فکتوری، کلاینت تنها به کلاس فکتوری و یک اینترفیس دسترسی دارد و کلاسهای فکتوری و اینترفیس، حق دسترسی به کلاسهای اصلی برنامه را دارند.

گام نخست: در ابتدا یک class library  به نام Interface ساخته و در آن یک کلاس با نام ICustomer  می سازیم   که متد Report() را معرفی می‌نماید.

  //Interface

namespace Interface
{
    public interface ICustomer
    {
        void Report();
    }
}

گام دوم: یک class library  به نام MainClass  ساخته و با Add Reference کلاس Interface را اضافه نموده، در آن دو کلاس با نام Customer1, Customer2 می‌سازیم و using Interface را Import می‌نماییم. هر دو کلاس از ICustomer  ارث می‌برند و  سپس متد Report() را در هر دو کلاس Implement می‌نماییم.

// Customer1
using System;
using Interface;

namespace MainClass
{
    public class Customer1 : ICustomer
    {
        public void Report()
        {           
            Console.WriteLine("این گزارش مخصوص مشتری نوع اول است");           
        }
    }
}

//Customer2
using System;
using Interface;

namespace MainClass
{
   public class Customer2 : ICustomer
    {
        public void Report()
        {           
            Console.WriteLine("این گزارش مخصوص مشتری نوع دوم است");           
        }
    }
}

گام سوم: یک class library  به نام FactoryClass  ساخته و با Add Reference کلاس Interface, MainClass را اضافه نموده، در آن یک کلاس با نام clsFactory  می سازیم و using Interface, using MainClass را Import می‌نماییم. پس از آن یک متد با نام getCustomerType ساخته که ورودی آن نوع مشتری از نوع int است و خروجی آن از نوع Interface-ICustomer و بر اساس کد نوع مشتری object را از کلاس Customer1 و یا Customer2 می‌سازیم و آن را return می نماییم.

//Factory
using System;
using Interface;
using MainClass;

namespace FactoryClass
{
    public class clsFactory
    {
        static public ICustomer getCustomerType(int intCustomerType)
        {
            ICustomer objCust;
            if (intCustomerType == 1)
            {
                objCust = new Customer1();
            }
            else if (intCustomerType == 2)
            {
                objCust = new Customer2();
            }
            else
            {
                return null;
            }
            return objCust;
        }
    }
}

گام چهارم (آخر): در قسمت UI   Client، کد نوع مشتری را از کاربر دریافت کرده و با Add Reference کلاس Interface, FactoryClass را اضافه نموده (دقت نمایید هیچ دسترسی به کلاس‌های اصلی وجود ندارد)، و using Interface,  using FactoryClass را Import می‌نماییم. از clsFactory تابع  getCustomerType را فراخوانی نموده (به آن کد نوع مشتری را پاس می‌دهیم) و خروجی آن را که از نوع اینترفیس است به یک object از نوع ICustomer  نسبت می‌دهیم. سپس از این object  متد Report را فراخوانی می‌نماییم. همانطور که از شکل و کدها مشخص است، هیچ رابطه ای بین UI(Client) و کلاسهای اصلی برقرار نیست.

//UI (Client)
using System;
using FactoryClass;
using Interface;

namespace DesignPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            int intCustomerType = 0;
            ICustomer objCust;
            Console.WriteLine("نوع مشتری را وارد نمایید");           
            intCustomerType = Convert.ToInt16(Console.ReadLine());
            objCust = clsFactory.getCustomerType(intCustomerType);
            objCust.Report();
            Console.ReadLine();
        }
    }
}

مطالب
Blazor 5x - قسمت سوم - مبانی Razor
پیش از شروع به کار توسعه‌ی برنامه‌های مبتنی بر Blazor، باید با مبانی Razor آشنایی داشت. Razor امکان ترکیب کدهای #C و HTML را در یک فایل میسر می‌کند. دستور زبان آن از @ برای سوئیچ بین کدهای #C و HTML استفاده می‌کند. کدهای Razor را می‌توان در فایل‌های cshtml. نوشت که عموما مخصوص صفحات و Viewها هستند و یا در فایل‌های razor. که برای توسعه‌ی کامپوننت‌های Balzor بکار گرفته می‌شوند. در اینجا مهم نیست که پسوند فایل مورد استفاده چیست؛ چون اصول razor بکار گرفته شده در آن‌ها یکی است. البته در اینجا تاکید ما بیشتر بر روی فایل‌های razor. است که در برنامه‌های مبتنی بر Blazor بکار گرفته می‌شوند.


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

برای پیاده سازی و اجرای مثال‌های این قسمت، نیاز به یک پروژه‌ی جدید Blazor WASM را داریم که می‌توان آن‌را با اجرای دستور dotnet new blazorwasm --hosted در یک پوشه‌ی خالی، ایجاد کرد.

یک نکته: دستور فوق به همراه یک سری پارامتر اختیاری مانند hosted-- نیز هست. برای مشاهده‌ی لیست آن‌ها دستور dotnet new blazorwasm --help را صادر کنید. برای مثال ذکر پارامتر hosted-- سبب می‌شود تا یک ASP.NET Core host نیز برای Blazor WebAssembly app ایجاد شده تولید شود.

حالت hosted-- آن یک چنین ساختاری را دارد که از سه پروژه و پوشه‌ی Client ،Server و Shared تشکیل می‌شود:


در اینجا یک پروژه‌ی خالی WASM ایجاد شده که برخلاف حالت معمولی dotnet new blazorwasm که در قسمت قبل آن‌را بررسی کردیم، دیگر از فایل استاتیک wwwroot\sample-data\weather.json در آن خبری نیست. بجای آن، یک پروژه‌ی استاندارد ASP.NET Core Web API را در پوشه‌ی جدید Server ایجاد کرده که کار ارائه‌ی اطلاعات این سرویس آب و هوا را انجام می‌دهد و برنامه‌ی WASM ایجاد شده، این اطلاعات را توسط HTTP Client خود، از سرور Web API دریافت می‌کند.

بنابراین اگر مدل برنامه‌ای که قصد دارید تهیه کنید، ترکیبی از یک Web API و WASM است، روش hosted--، آغاز آن‌را بسیار ساده می‌کند.

نکته: روش اجرای این نوع برنامه‌ها با اجرای دستور dotnet run در داخل پوشه‌ی Server پروژه، انجام می‌شود. با اینکار هم سرور ASP.NET Core آغاز می‌شود و هم برنامه‌ی WASM توسط آن ارائه می‌گردد. در این حالت اگر آدرس https://localhost:5001 را در مرورگر باز کنیم، هم قسمت‌های بدون نیاز به سرور پروژه‌ی WASM قابل دسترسی است (مانند کار با شمارشگر آن) و هم قسمت دریافت اطلاعات از سرور آن، در منوی Fetch Data.


شروع به کار با Razor

پس از ایجاد یک پروژه‌ی جدید WASM، به فایل Client\Pages\Index.razor آن مراجعه کرده و محتوای پیش‌فرض آن‌را بجز سطر اول زیر، حذف می‌کنیم:
@page "/"
این سطر، بیانگر مسیریابی منتهی به کامپوننت جاری است. یعنی با گشودن برنامه‌ی WASM در مرورگر و مراجعه به ریشه‌ی سایت، محتوای این کامپوننت را مشاهده خواهیم کرد.
در فایل‌های razor. می‌توان ترکیبی از کدهای #C و HTML را نوشت. برای مثال:
@page "/"

<p>Hello, @name</p>

@code
{
    string name = "Vahid N.";
}
در اینجا قصد داریم مقدار یک متغیر را در یک پاراگراف درج کنیم. به همین جهت برای تعریف آن و شروع به کدنویسی می‌توان با تعریف یک قطعه کد که در فایل‌های razor با code@ شروع می‌شود، اینکار را انجام داد. در این قطعه کد، نوشتن هر نوع کد #C ای مجاز است که نمونه‌ای از آن‌را در اینجا با تعریف یک متغیر مشاهده می‌کنید. اکنون برای درج مقدار این متغیر در بین کدهای HTML از حرف @ استفاده می‌کنیم؛ مانند name@ در اینجا. نمونه‌ای از خروجی تغییرات فوق را در تصویر زیر مشاهده می‌کنید:


یک نکته: با توجه به اینکه تغییرات زیادی را در فایل جاری اعمال خواهیم کرد، بهتر است برنامه را با دستور dotnet watch run اجرا کرد، تا این تغییرات را تحت نظر قرار داده و آن‌ها را به صورت خودکار کامپایل کند. به این صورت دیگر نیازی نخواهد بود به ازای هر تغییر، یکبار دستور dotnet run اجرا شود.

در زمان درج متغیرهای #C در بین کدهای HTML توسط razor، استفاده از تمام متدهای الحاقی زبان #C نیز مجاز هستند؛ مانند:
 <p>Hello, @name.ToUpper()</p>
بنابراین درج حرف @ در بین کدهای HTML به این معنا است که به کامپایلر razor اعلام می‌کنیم، پس از این حرف، هر عبارتی که قرار می‌گیرد، یک عبارت معتبر #C است.

یا حتی می‌توان یک متد جدید را مانند CustomToUpper در قطعه کد razor، تعریف کرد و از آن به صورت زیر استفاده نمود:
@page "/"

<p>Hello, @name.ToUpper()</p>
<p>Hello, @CustomToUpper(name)</p>

@code
{
    string name = "Vahid N.";

    string CustomToUpper(string value) => value.ToUpper();
}
در این مثال‌ها، ابتدای عبارت #C تعریف شده با حرف @ شروع می‌شود و انتهای آن‌را خود کامپایلر razor بر اساس بسته شدن تگ p تعریف شده، تشخیص می‌دهد. اما اگر قصد داشته باشیم برای مثال جمع دو عدد را در اینجا محاسبه کنیم چطور؟
<p>Let's add 2 + 2 : @2 + 2 </p>
در این حالت امکان تشخیص ابتدا و انتهای عبارت #C توسط کامپایلر میسر نیست. برای رفع این مشکل می‌توان از پرانتزها استفاده کرد:
<p>Let's add 2 + 2 : @(2 + 2) </p>
نمونه‌ی دیگر نیاز به تعریف ابتدا و انتهای یک قطعه کد، در حین تعریف مدیریت کنندگان رویدادها است:
<button @onclick="@(()=>Console.WriteLine("Test"))">Click me</button>
در اینجا onclick@ مشخص می‌کند که با کلیک بر روی این دکمه قرار است قطعه کد #C ای اجرا شود. سپس با استفاده از ()@ محدوده‌ی این قطعه کد، مشخص می‌شود و اکنون در داخل آن می‌توان یک anonymous function را تعریف کرد که خروجی آن را در قسمت console ابزارهای توسعه دهندگان مرورگر می‌توان مشاهده کرد:


در اینجا اگر از Console.WriteLine("Test")@ استفاده می‌شد، به معنای انتساب یک رشته‌ی محاسبه شده به رویداد onclick بود که مجاز نیست.
روش دیگر انجام اینکار به صورت زیر است:
@page "/"

<button @onclick="@WriteLog">Click me 2</button>

@code
{
    void WriteLog()
    {
        Console.WriteLine("Test");
    }
}
می‌توان یک متد void را تعریف کرد و سپس فقط نام آن‌را توسط @ به onlick انتساب داد. ذکر این نام، اشاره‌گری خواهد بود به متد اجرا نشده‌ی WriteLog. در این حالت اگر نیاز به ارسال پارامتری به متد WriteLog بود، چطور؟
@page "/"

<button @onclick="@(()=>WriteLogWithParam("Test 3"))">Click me 3</button>

@code
{
    void WriteLogWithParam(string value)
    {
        Console.WriteLine(value);
    }
}
در این حالت نیز می‌توان از روش بکارگیری anonymous function‌ها برای تعریف پارامتر استفاده کرد.

یک نکته: اگر به اشتباه بجای WriteLogWithParam، همان WriteLog قبلی را بنویسیم، کامپایلر (در حال اجرای توسط دستور dotnet watch run) خطای زیر را نمایش می‌دهد؛ پیش از اینکه برنامه در مرورگر اجرا شود:
BlazorRazorSample\Client\Pages\Index.razor(12,25): error CS1501: No overload for method 'WriteLog' takes 1 arguments


امکان تعریف کلاس‌ها در فایل‌های razor.

در فایل‌های razor.، محدود به تعریف یک سری متدها و متغیرهای ساده نیستیم. در اینجا امکان تعریف کلاس‌ها نیز وجود دارد و همچنین می‌توان از کلاس‌های خارجی (کلاس‌هایی که خارج از فایل razor جاری تعریف شده‌اند) نیز استفاده کرد.
@page "/"

<p>Hello, @StringUtils.MyCustomToUpper(name)</p>

@code
{
    public class StringUtils
    {
        public static string MyCustomToUpper(string value) => value.ToUpper();
    }
}
برای نمونه در اینجا یک کلاس کمکی را جهت تعریف متد MyCustomToUpper، اضافه کرده‌ایم. در ادامه نحوه‌ی استفاده از این متد را در پاراگراف تعریف شده، مشاهده می‌کنید که همانند کار با کلاس و متدهای متداول #C است.
البته این کلاس را تنها می‌توان داخل همین کامپوننت استفاده کرد. برای اینکه بتوان از امکانات این کلاس، در سایر کامپوننت‌ها نیز استفاده کرد، می‌توان آن‌را در پروژه‌ی Shared قرار داد. اگر به تصویر ابتدای مطلب جاری دقت کنید، سه پروژه ایجاد شده‌است:
الف) پروژه‌ی کلاینت: که همان WASM است.
ب) پروژه‌ی سرور: که یک پروژه‌ی ASP.NET Core Web API ارائه کننده‌ی سرویس و API آب و هوا است و همچنین هاست کننده‌ی WASM ما.
ج) پروژه‌ی Shared: کدهای این پروژه، بین هر دو پروژه به اشتراک گذاشته می‌شوند و برای مثال محل مناسبی است برای تعریف DTO ها. برای نمونه WeatherForecast.cs قرار گرفته‌ی در آن، DTO یا data transfer object سرویس API برنامه است که قرار است به کلاینت بازگشت داده شود. به این ترتیب دیگر نیازی نخواهد بود تا این تعاریف را در پروژه‌های سرور و کلاینت تکرار کنیم و می‌توان کدهای اینگونه را به اشتراک گذاشت.
کاربرد دیگر آن تعریف کلاس‌های کمکی است؛ مانند StringUtils فوق. به همین به پروژه‌ی Shared مراجعه کرده و کلاس StringUtils را به صورت زیر در آن تعریف می‌کنیم (و یا حتی می‌توان این قطعه کد را داخل یک پوشه‌ی جدید، در همان پروژه‌ی WASM نیز قرار داد):
namespace BlazorRazorSample.Shared
{
    public class StringUtils
    {
        public static string MyNewCustomToUpper(string value) => value.ToUpper();
    }
}
اگر به فایل‌های csproj دو پروژه‌ی سرور و کلاینت جاری مراجعه کنیم، از پیش، مدخلی را به فایل Shared\BlazorRazorSample.Shared.csproj دارند. بنابراین جهت معرفی این اسمبلی به آن‌ها، نیاز به کار خاصی نیست و از پیش، ارجاعی به آن تعریف شده‌است.

پس از آن روش استفاده‌ی از این کلاس کمکی خارجی اشتراکی به صورت زیر است:
@page "/"

@using BlazorRazorSample.Shared

<p>Hello, @StringUtils.MyNewCustomToUpper(name)</p>
ابتدا فضای نام این کلاس را با استفاده از using@ مشخص می‌کنیم و سپس امکان دسترسی به امکانات آن میسر می‌شود.

یک نکته: می‌توان به فایل Client\_Imports.razor مراجعه و مدخل زیر را به انتهای آن اضافه کرد:
@using BlazorRazorSample.Shared
به این ترتیب دیگر نیازی به ذکر این using@ تکراری، در هیچکدام از فایل‌های razor. پروژه‌ی کلاینت نخواهد بود؛ چون تعاریف درج شده‌ی در فایل Client\_Imports.razor سراسری هستند.


کار با حلقه‌ها در فایل‌های razor.

همانطور که عنوان شد، یکی از کاربردهای پروژه‌ی Shared، امکان به اشتراک گذاشتن مدل‌ها، در برنامه‌های کلاینت و سرور است. برای مثال یک پوشه‌ی جدید Models را در این پروژه ایجاد کرده و کلاس MovieDto را به صورت زیر در آن تعریف می‌کنیم:
using System;

namespace BlazorRazorSample.Shared.Models
{
    public class MovieDto
    {
        public string Title { set; get; }

        public DateTime ReleaseDate { set; get; }
    }
}
سپس به فایل Client\_Imports.razor مراجعه کرده و فضای نام این پوشه را اضافه می‌کنیم؛ تا دیگر نیازی به تکرار آن در تمام فایل‌های razor. برنامه‌ی کلاینت نباشد:
@using BlazorRazorSample.Shared.Models
اکنون می‌خواهیم لیستی از فیلم‌ها را در فایل Client\Pages\Index.razor نمایش دهیم:
@page "/"

<div>
    <h3>Movies</h3>
    @foreach(var movie in movies)
    {
        <p>Title: <b>@movie.Title</b></p>
        <p>ReleaseDate: @movie.ReleaseDate.ToString("dd MMM yyyy")</p>
    }
</div>

@code
{
    List<MovieDto> movies = new List<MovieDto>
    {
        new MovieDto
        {
            Title = "Movie 1",
            ReleaseDate = DateTime.Now.AddYears(-1)
        },
        new MovieDto
        {
            Title = "Movie 2",
            ReleaseDate = DateTime.Now.AddYears(-2)
        },
        new MovieDto
        {
            Title = "Movie 3",
            ReleaseDate = DateTime.Now.AddYears(-3)
        }
    };
}
در اینجا در ابتدا لیستی از MovieDto‌ها در قسمت code@ تعریف شده و سپس روش استفاده‌ی از یک حلقه‌ی foreach سی‌شارپ را در کدهای razor نوشته شده، مشاهده می‌کنید که این خروجی را ایجاد می‌کند:


یک نکته: در حین تعریف فیلدهای code@، امکان استفاده‌ی از var وجود ندارد؛ مگر اینکه از آن بخواهیم در داخل بدنه‌ی یک متد استفاده کنیم.

و یا نمونه‌ی دیگری از حلقه‌های #‍C مانند for را می‌توان به صورت زیر تعریف کرد:
    @for(var i = 0; i < movies.Count; i++)
    {
        <div style="background-color: @(i % 2 == 0 ? "blue" : "red")">
            <p>Title: <b>@movies[i].Title</b></p>
            <p>ReleaseDate: @movies[i].ReleaseDate.ToString("dd MMM yyyy")</p>
        </div>
    }
در اینجا روش تغییر پویای background-color هر ردیف را نیز به کمک کدهای razor، مشاهده می‌کنید. اگر شماره‌ی ردیفی زوج بود، با آبی نمایش داده می‌شود؛ در غیراینصورت با قرمز. در اینجا نیز از ()@ برای تعیین محدوده‌ی کدهای #C نوشته شده، کمک گرفته‌ایم.


نمایش شرطی عبارات در فایل‌های razor.

اگر به مثال توکار Client\Pages\FetchData.razor مراجعه کنیم (مربوط به حالت host-- که در ابتدای مطلب عنوان شد)، کدهای زیر قابل مشاهده هستند:
@page "/fetchdata"
@using BlazorRazorSample.Shared
@inject HttpClient Http

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
    }

}
در این مثال، روش کار با یک سرویس تزریق شده‌ی async که قرار است از Web API اطلاعاتی را دریافت کند، مشاهده می‌کنید. در اینجا برخلاف مثال قبلی ما، از روال رویدادگردان OnInitializedAsync برای مقدار دهی لیست یا آرایه‌ای از اطلاعات وضعیت هوا استفاده شده‌است (و نه به صورت مستقیم در یک فیلد قسمت code@). این مورد جزو life-cycle‌های کامپوننت‌های razor است که در قسمت‌های بعد بیشتر بررسی خواهد شد. متد OnInitializedAsync برای بارگذاری اطلاعات یک سرویس از راه دور استفاده می‌شود و در اولین بار اجرای کامپوننت فراخوانی خواهد شد. نکته‌ی مهمی که در اینجا وجود دارد، نال بودن فیلد forecasts در زمان رندر اولیه‌ی کامپوننت جاری است؛ از این جهت که کار دریافت اطلاعات از سرور زمان‌بر است ولی رندر کامپوننت، به صورت آنی صورت می‌گیرد. در این حالت زمانیکه نوبت به اجرای foreach (var forecast in forecasts)@ می‌رسد، برنامه با یک استثنای نال بودن forecasts، متوقف خواهد شد؛ چون هنوز کار OnInitializedAsync به پایان نرسیده‌است:


 برای رفع این مشکل، ابتدا یک if@ مشاهده می‌شود، تا نال بودن forecasts را بررسی کند:
@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
و همچنین عبارت در حال بارگذاری را نمایش می‌دهد. سپس در قسمت else آن، نمایش اطلاعات دریافت شده را توسط یک حلقه‌ی foreach مشاهده می‌کنید. با مقدار دهی forecasts در متد OnInitializedAsync، مجددا کار رندر جدول انجام خواهد شد.


روش نمایش عبارات HTML در فایل‌های razor.

فرض کنید عنوان اول فیلم مثال جاری، به همراه یک تگ HTML هم هست:
new MovieDto
{
   Title = "<i>Movie 1</i>",
   ReleaseDate = DateTime.Now.AddYears(-1)
},
در این حالت اگر برنامه را اجرا کنیم، خروجی آن دقیقا به صورت <Title: <i>Movie 1</i خواهد بود. این مورد به دلایل امنیتی انجام شده‌است. اگر پیشتر تگ‌های HTML را تمیز کرده‌اید و مطمئن هستید که خطری را ایجاد نمی‌کنند، می‌توانید با استفاده از روش زیر، آن‌ها را رندر کرد:
<p>Title: <b>@((MarkupString)movie.Title)</b></p>


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-03.zip
برای اجرای آن وارد پوشه‌ی Server شده و دستور dotnet run را اجرا کنید.
مطالب
React 16x - قسمت 30 - React Hooks - بخش 1 - معرفی useState و useEffect
با استفاده از React Hooks که در نگارش 16.7.0 آن معرفی شدند، می‌توان در کامپوننت‌های تابعی «تا پیش از این» بدون حالت، به state و تمام قابلیت‌های دیگر React، دسترسی یافت. جهت یادآوری، در قسمت 8 این سری، کامپوننت‌های تابعی بدون حالت را معرفی کردیم. تا پیش از معرفی React Hooks، برای ردیابی تغییرات مقادیری خاص، می‌بایستی آن‌ها را در خاصیت state کامپوننت‌هایی که به صورت کلاس تعریف شده بودند، قرار می‌دادیم. بنابراین class components، تنها نوع کامپوننت‌هایی در React بودند که دسترسی به state را داشتند. شبیه به همین مورد، برای life-cycle hooks معرفی شده‌ی در قسمت 9 برقرار بود. برای مثال متد componentDidMount، تنها در class components، جهت دسترسی به یک API خارجی و انجام اعمال Ajax ای، قابل تعریف بود و کار کامپوننت‌های تابعی بدون حالت، بیشتر نمایش عناصر HTML و دریافت مقادیر خود از class components، توسط props بود. به این ترتیب امکان تجزیه‌ی یک UI پیچیده، به یک component tree با خوانایی بیشتری، میسر می‌شد. اما با معرفی React v16.7.0، از لحاظ فنی دیگر الزاما نیازی به class components وجود ندارد و می‌توان با استفاده از React Hooks، تمام قابلیت‌هایی را که پیشتر فقط توسط class components در اختیار داشتیم، اکنون با کامپوننت‌های تابعی نیز پیاده سازی کنیم.


برپایی پیش‌نیازها

در اینجا برای بررسی React Hooks، یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> npm i -g create-react-app
> create-react-app sample-30
> cd sample-30
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

همچنین اگر به فایل package.json موجود در ریشه‌ی پروژه دقت کنیم، برای کار با React-hooks، نیاز است نگارش بسته‌های React و React-dom، حداقل مساوی 16.7 باشند که در زمان نگارش این مطلب، نگارش 16.12.0 آن به صورت خودکار نصب می‌شود. بنابراین بدون مشکلی می‌توانیم شروع به کار با React hooks کنیم.


معرفی useState Hook

در اینجا قصد داریم یک شمارشگر را به همراه یک دکمه، در صفحه نمایش دهیم؛ بطوریکه این شمارشگر، تعداد بار کلیک بر روی دکمه را ردیابی می‌کند. از پیش می‌دانیم که برای ردیابی مقدار تعداد بار کلیک شدن، باید متغیر آن‌را درون state یک class component قرار داد:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = { count: 0 };

  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <button onClick={this.incrementCount} className="btn btn-primary m-3">
        I was clicked {this.state.count} times!
      </button>
    );
  }
}

export default App;
در اینجا یک class component متداول را داریم که در آن دکمه‌ای تعریف شده‌است. سپس برای شمارش تعداد بار کلیک بر روی آن، خاصیت count را به شیء منتسب به state، با مقدار اولیه‌ی صفر، اضافه کرده‌ایم. اکنون هربار که بر روی آن کلیک می‌شود، رویدادگردان incrementCount فراخوانی شده و توسط متد this.setState، مقدار پیشین خاصیت count، بر اساس مقدار فعلی آن، یک واحد افزایش می‌یابد. نتیجه‌ی آن نیز در برچسب دکمه، نمایش داده می‌شود:


اکنون می‌خواهیم همین کامپوننت را توسط React hooks بازنویسی کنیم. در ابتدا، فایل app.js را به App‍Class.js، تغییر نام می‌دهیم تا نگارش قبلی class component را برای مقایسه، در اختیار داشته باشیم. در ادامه فایل جدید AppFunction.js را برای بازنویسی آن توسط یک کامپوننت تابعی، توسط میانبرهای imrc و سپس sfc در VSCode، ایجاد می‌کنیم. البته این تغییر نام فایل‌ها، نیاز به تغییر نام ماژول‌های import شده‌ی در فایل index.js را نیز به صورت زیر دارد:
//import App from "./App‍Class";
import App from "./AppFunction";

اولین سؤالی که اینجا مطرح می‌شود، این است: در این کامپوننت تابعی جدید، state را از کجا بدست بیاوریم؟
با React Hooks، بجای تعریف یک state به صورت خاصیت، آن‌را صرفا use می‌کنیم و این دقیقا نام اولین React Hooks ای است که بررسی می‌کنیم؛ یا همان useState. بنابراین ابتدا این شیء را import خواهیم کرد:
import React, { useState } from 'react';
useState یک تابع است و باید در ابتدای کامپوننت، مورد استفاده قرار گیرد. این متد برای شروع به کار، نیاز به یک state آغازین را دارد؛ دقیقا مانند همان‌کاری که در class component فوق انجام دادیم:
const App = () => {
    const [count, setCount] = useState(0);
در اینجا عدد صفر، همان مقدار آغازین متغیر count است (شبیه به کاری که در state = { count: 0 } انجام دادیم). سپس اولین خروجی متد useState که داخل یک آرایه مشخص شده‌است (JavaScript array destructuring ؛ با مزیت امکان انتخاب نام‌هایی دلخواه، بدون نیاز به تعریف alias، برخلاف حالت object destructuring)، همان متغیر count است که توسط state ردیابی خواهد شد. اینبار بجای متد this.setState قبلی که یک متد عمومی بود، متدی اختصاصی را صرفا جهت تغییر مقدار همین متغیر count، به نام setCount به عنوان دومین خروجی متد useState، تعریف می‌کنیم. در حقیقت تا اینجا امضای متد جنریک useState استفاده شده، به صورت زیر تغییر کرده‌است:
useState<number>(initialState: number | (() => number)): [number, React.Dispatch<React.SetStateAction<number>>]
متد useState، یک initialState را دریافت می‌کند و سپس یک عدد را (در اینجا چون مقدار آغازین، عددی است)، به همراه یک متد، جهت تغییر state آن، بازگشت می‌دهد:
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <button onClick={incrementCount} className="btn btn-primary m-3">
      I was clicked {count} times!
    </button>
  );
};

export default App;
- پس از تعریف useState، متد رویدادگردان onClick را همانند قبل تعریف می‌کنیم؛ با یک تغییر مهم: چون این متد داخل یک کامپوننت تابعی تعریف شده‌است، باید با const شروع شود و همانند یک متغیر که به آن متدی انتساب داده شده، معرفی گردد. پیشتر incrementCount تعریف شده‌ی داخل یک class component، یک خاصیت بود که متدی به آن انتساب داده شده بود.
- همچنین در اینجا (داخل این متد) دیگر خبری از this‌ها نیست؛ onClick، مستقیما به متغیر incrementCount اشاره می‌کند و {count} نیز مستقیما از خروجی useState، که به مقدار جاری count اشاره می‌کند، تامین می‌شود.
- اکنون با هربار کلیک بر روی این دکمه، متد منتسب به متغیر incrementCount فراخوانی شده و در داخل آن، همان متد setCount را جهت به روز رسانی state، فراخوانی می‌کنیم (بجای فراخوانی this.setState عمومی قبلی). در اینجا ابتدا مقدار جاری متغیر count در state، دریافت شده و سپس یک واحد به آن اضافه می‌شود. امضای متد جنریک setCount به صورت زیر است:
 const setCount: (value: React.SetStateAction<number>) => void


استفاده از مقدار قبلی state توسط useState

زمانیکه متد this.setState فراخوانی می‌شود، اینکار سبب در صف قرار گرفتن رندر مجدد کامپوننت جاری خواهد شد. همچنین اعمال این متد نیز ممکن است در صف قرار گیرد. یعنی اگر پس از فراخوانی this.setState، سعی در خواندن state به روز شده را داشته باشیم، ممکن است مقدار اشتباهی را دریافت کنیم:
  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  };
برای نمونه در فراخوانی فوق، منظور از this.state.count، مقدار جاری یا همان مقدار قبلی count است که قرار است یک واحد به آن اضافه شود. اما چون متد this.setState کار به روز رسانی‌های state را نیز در صف قرار می‌دهد، دفعه‌ی بعدی که بر روی آن کلیک شد، الزامی ندارد که this.state.count، حتما در همان لحظه به روز رسانی شده باشد. برای رفع این مشکل می‌توان نوشت:
  incrementCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };
prevState در اینجا یعنی state قبلی و توسط متد setState، به صورت یک arrow function قابل دریافت است که در نهایت یک شیء را باز می‌گرداند. اکنون بر اساس state قبلی می‌توان به روز رسانی دقیقی را انجام داد.

این نکته در مورد کامپوننت‌های تابعی نیز وجود دارد:
  const incrementCount = () => {
    setCount(count + 1);
  };
در اینجا متغیر count، همانند خاصیت this.state.count کامپوننت‌های کلاسی عمل می‌کند. بنابراین الزامی ندارد که با هر بار فراخوانی setCount، مقدار جاری متغیر count، دقیقا به مقدار قبلی تنظیم شده‌ی آن اشاره کند. برای انجام مشابه اینکار با کامپوننت‌های تابعی که از useState استفاده می‌کنند، می‌توان به صورت زیر عمل کرد:
const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };
در اینجا نیز امکان دسترسی به مقدار قبلی count، توسط یک arrow function وجود دارد که برخلاف حالت prevState، فقط یک مقدار عددی مرتبط با count را باز می‌گرداند.


به روز رسانی بیش از یک خاصیت در state

فرض کنید قصد داریم به مثال جاری، یک مربع را در صفحه اضافه کنیم که با کلیک بر روی آن، رنگش تغییر می‌کند (خاموش و روشن می‌شود):


در حالت AppClass یا کامپوننت کلاسی، کدهای برنامه به صورت زیر تغییر می‌کنند:
import "./App.css";

import React, { Component } from "react";

class App extends Component {
  state = {
    count: 0,
    isOn: false
  };

  incrementCount = () => {
    this.setState(prevState => ({
      count: prevState.count + 1
    }));
  };

  toggleLight = () => {
    this.setState(prevState => ({
      isOn: !prevState.isOn
    }));
  };

  render() {
    return (
      <>
        <h1>App Class</h1>
        <h2>Counter</h2>
        <button onClick={this.incrementCount} className="btn btn-primary m-3">
          I was clicked {this.state.count} times!
        </button>

        <h2>Toggle Light</h2>
        <div
          style={{
            height: "50px",
            width: "50px",
            cursor: "pointer"
          }}
          className={
            this.state.isOn ? "alert alert-info m-3" : "alert alert-warning m-3"
          }
          onClick={this.toggleLight}
        />
      </>
    );
  }
}

export default App;
توضیحات:
- در متد رندر، نیاز است تا تنها یک child، بازگشت داده شود. به همین جهت می‌توان از React.Fragment، برای محصور سازی المان‌های تعریف شده، استفاده کرد. البته در React 16.7.0، دیگر نیازی به ذکر صریح React.Fragment نبوده و فقط می‌توان نوشت </><> تا بیانگر یک فرگمنت باشد.
- سپس یک div تعریف شده که با استفاده از ویژگی style، یک سری شیوه‌نامه‌ی ابتدایی، مانند طول و عرض و نوع اشاره‌گر ماوس آن، تعیین شده‌اند.
- اکنون برای اینکه بتوان با کلیک بر روی این div، رنگ آن‌را تغییر داد، نیاز است بتوان توسط متغیری، مقدار خاموش و روشن بودن را ردیابی کرد. به همین جهت خاصیت جدید isOn را به state اضافه می‌کنیم.
- در آخر، رویداد onClick این div را به متد رویدادگران toggleLight متصل می‌کنیم تا توسط آن و فراخوانی this.setState، بتوان مقدار قبلی isOn را در state، دریافت و سپس آن‌را معکوس کرد و بجای مقدار جاری isOn در state درج کنیم. این فراخوانی، سبب رندر مجدد کامپوننت جاری شده و در نتیجه‌ی آن، مقدار className را بر اساس مقدار this.state.isOn، به صورت پویا تغییر می‌دهد.

برای مشاهده‌ی خروجی برنامه در این حالت، نیاز است به index.js مراجعه و تغییر زیر را اعمال کرد تا کامپوننت App، از ماژول App‍Class تامین شود:
import App from "./App‍Class";
//import App from "./AppFunction";

اکنون قصد داریم دقیقا معادل همین قطعه کد را در کامپوننت تابعی خود پیاده سازی کنیم. به همین جهت به فایل src\AppFunction.js بازگشته و تغییرات زیر را اعمال می‌کنیم:
import React, { useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);
  const [isOn, setIsOn] = useState(false);

  const incrementCount = () => {
    setCount(prevCount => prevCount + 1);
  };

  const toggleLight = () => {
    setIsOn(prevIsOn => !prevIsOn);
  };

  return (
    <>
      <h1>App Function</h1>
      <h2>Counter</h2>
      <button onClick={incrementCount} className="btn btn-primary m-3">
        I was clicked {count} times!
      </button>

      <h2>Toggle Light</h2>
      <div
        style={{
          height: "50px",
          width: "50px",
          cursor: "pointer"
        }}
        className={isOn ? "alert alert-info m-3" : "alert alert-warning m-3"}
        onClick={toggleLight}
      />
    </>
  );
};

export default App;
توضیحات:
- اگر دقت کنید، کلیات این کامپوننت تابعی، با کامپوننت کلاسی، آنچنان متفاوت نیست. متد رندر آن دقیقا همان markup را بازگشت می‌دهد؛ با یک تفاوت مهم: در اینجا دیگر نیازی به ذکر thisها نیست، چون تمام ارجاعات، به متغیرهای داخل تابع App انجام شده‌است و نه به متدها و یا خاصیت‌های یک کلاس.
- مرحله‌ی بعد تغییر، جایگزینی this.state.isOn قبلی، با یک متغیر درون تابع App است. به همین جهت در اینجا یک useState دیگر را تعریف می‌کنیم. هر useState، تنها به قسمتی از state اشاره می‌کند و مانند خاصیت کلی state مربوط به یک کلاس نیست. همچنین در خاصیت state یک کلاس، مقدار آن همواره به یک شیء اشاره می‌کند؛ اما با useState چنین اجباری وجود ندارد و می‌تواند هر نوع مجاز جاوا اسکریپتی باشد. برای مثال در اینجا مقدار اولیه‌ی آن به false تنظیم شده‌است. پس از آن، خروجی این متد، قسمتی از state را که کنترل می‌کند، به نام متغیر isOn و تنظیم کننده‌ی آن‌را به نام متد setIsOn، معرفی خواهد کرد. متد useState، یک متد جنریک است. یعنی نوع خروجی‌های آن بر اساس نوع مقدار اولیه‌ای که به آن انتساب داده می‌شود، تعیین می‌شود. برای مثال اگر نوع مقدار اولیه‌ی آن، Boolean باشد، به صورت خودکار نوع متغیر و پارامتر متد خروجی از آن نیز Boolean خواهند بود.
- در آخر خاصیت toggleLight کلاس کامپوننت، تبدیل به یک متغیر یا ثابت، در تابع جاری App می‌شود و بجای this.setState کلی قبلی، از متد اختصاصی‌تر setIsOn، برای تغییر مقدار state متناظر، کمک گرفته خواهد شد. در اینجا با استفاده از prevIsOn، به مقدار دقیق پیشین متغیر isOn در state، دسترسی یافته و سپس آن‌را معکوس می‌کنیم.

برای مشاهده‌ی خروجی برنامه در این حالت، نیاز است به index.js مراجعه و تغییر زیر را اعمال کرد تا کامپوننت App، از ماژول App‍Function تامین شود:
// import App from "./App‍Class";
import App from "./AppFunction";


معرفی useEffect Hook

فرض کنید قصد داریم برچسب دکمه‌ی «I was clicked {this.state.count} times» را در المان Title صفحه، نمایش دهیم. برای انجام چنین کاری نیاز است با DOM API تعامل داشته باشیم؛ اما پیشتر یک چنین کاری را تنها با class components می‌شد انجام داد. برای رفع این محدودیت در کامپوننت‌های تابعی، hook جدیدی به نام useEffect، ارائه شده‌است که باید import شود:
import React, { useEffect, useState } from "react";
اکنون این سؤال مطرح می‌شود که در متد useEffect، واژه‌ی Effect به چه چیزی اشاره می‌کند؟
در اینجا effect به معنای side effect و یا اثرات جانبی است؛ مانند: تعامل با یک API خارجی، کار با API مرورگر و کلا هر جائیکه در برنامه با دنیای خارج تعاملی وجود دارد، به عنوان یک side effect شناخته می‌شود. بنابراین با استفاده متد useEffect، می‌توان در کامپوننت‌های تابعی نیز با دنیای خارج، تعامل برقرار کرد.
import React, { useEffect, useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You have clicked ${count} times`;
  });
متد useEffect، یک تابع را به عنوان ورودی دریافت کرده (effect function) و آن‌را پس از هر بار رندر کامپوننت جاری، اجرا می‌کند. برای مثال با تغییر state، کار رندر کامپوننت جاری، در صف قرار می‌گیرد و پس از اتمام رندر، تابع effect ذکر شده، اجرا می‌شود. برای مثال در اینجا پس از هر بار رندر کامپوننت،  document.title با عبارتی که به همراه تعداد بار کلیک کردن بر روی دکمه‌است، به روز رسانی می‌شود:


در اولین بار اجرای برنامه، عبارت «You have clicked 0 times»، در عنوان صفحه‌ی جاری، ظاهر می‌شود که از مقدار پیش‌فرض count، استفاده کرده‌است. اکنون اگر بر روی دکمه، کلیک کنیم، پس از تغییر برچسب دکمه (یا همان رندر کامپوننت، پس از تغییری در state)، آنگاه عنوان نمایش داده شده‌ی در مرورگر نیز تغییر می‌کند.

یک نکته: چون useEffect دارای همان scope متغیر count است، نیاز به API خاصی برای خواندن مقدار آن در اینجا نیست.

سؤال: برای پیاده سازی چنین قابلیتی در یک کامپوننت کلاسی چه باید کرد؟ در این مثال، در ابتدای کار باید مقدار پیش‌فرض موجود در state را در عنوان صفحه مشاهده کرد و پس از هربار به روز رسانی state نیز باید این عنوان، تغییر کند.
برای پیاده سازی معادل متد useEffect ای که در یک کامپوننت تابعی استفاده شد، در اینجا باید از دو life-cycle hook متفاوت، به نام‌های componentDidMount و componentDidUpdate، استفاده کنیم:
class App extends Component {

  componentDidMount() {
    document.title = `You have been clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You have been clicked ${this.state.count} times`;
  }
- برای به روز رسانی عنوان صفحه، در اولین بار نمایش آن، باید از متد componentDidMount استفاده کرد.
- همچنین چون می‌خواهیم به ازای هر تغییری در state نیز این عنوان تغییر کند (با هر بار کلیک بر روی دکمه)، باید از متد componentDidUpdate هم استفاده کنیم.


پاکسازی اثرات جانبی در useEffect Hook

فرض کنید قصد داریم موقعیت فعلی کرسر ماوس را در مرورگر نمایش دهیم. برای انجام اینکار در کامپوننت کلاسی، می‌توان از متد componentDidMount جهت دسترسی به DOM API و استفاده از متد addEventListener آن، برای گوش فرا دادن به حرکات کرسر ماوس، استفاده کرد:
class App extends Component {

  componentDidMount() {
    // ...
    window.addEventListener("mousemove", this.handleMouseMove);
  }
 دومین پارامتر این متد، یک callback function است که این حرکات را ردیابی می‌کند:
  handleMouseMove = event => {
    this.setState({
      x: event.pageX,
      y: event.pageY
    });
  };
در اینجا می‌توان با استفاده از شیء رخداد دریافتی، موقعیت x و y کرسر ماوس را در صفحه دریافت کرد و سپس با فراخوانی متد this.setState، این خواص را در state کامپوننت، اضافه و یا به روز رسانی نمود. بنابراین در این حالت الزامی به تعریف این خواص در شیء منتسب به state نیست؛ اما حداقل با تعریف آن‌ها می‌توان مقدار اولیه‌ای را برایشان درنظر گرفت:
class App extends Component {
  state = {
    //...
    x: 0,
    y: 0
  };
در آخر برای نمایش این اطلاعات موجود در state، می‌توان چنین المان‌هایی را به متد رندر کامپوننت، اضافه کرد:
<h2>Mouse Position</h2>
<p>X position: {this.state.x}</p>
<p>Y position: {this.state.y}</p>
بنابراین ردیابی تغییرات محل کرسر ماوس نیز یک side effect است که برای دسترسی به آن، نیاز است با window API کار کرد و این side effect‌ها دو نوع هستند:
- تعدادی از آن‌ها نیازی به پاکسازی و خارج شدن از حافظه را ندارند؛ مانند به روز رسانی عنوان صفحه در مرورگر. می‌توان یک چنین side effect هایی را اجرا و سپس آن‌ها را فراموش کرد.
- اما تعدادی از side effect‌ها را حتما باید پاکسازی کرد؛ مانند mousemove listener تعریف شده‌ی در مثال فوق. در اینجا زمانیکه کامپوننت جاری mount می‌شود، این listener را تعریف می‌کنیم؛ اما با Unmount شدن آن، باید این listener را حذف کرد تا برنامه دچار نشتی حافظه نشود (اگر اینکار انجام نشود، در این مثال مرورگر هنگ خواهد کرد!). روش انجام اینکار در متد componentWillUnmount، به صورت زیر است:
  componentWillUnmount() {
    window.removeEventListener("mousemove", this.handleMouseMove);
  }
سؤال: یک چنین قابلیتی را چگونه می‌توان در یک کامپوننت تابعی تعریف کرد؟
در این حالت نیز می‌توان از متد useEffect استفاده کرد. البته ابتدا باید state شیء ای را برای نگهداری اطلاعات به روز موقعیت مکانی کرسر ماوس، ایجاد کرد:
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
همانطور که عنوان شد، متد useState، برخلاف خاصیت کلی state یک کامپوننت کلاسی که فقط اشیاء را می‌پذیرد، هر نوع جاوا اسکریپتی را می‌تواند بپذیرد که در اینجا برای نمونه به یک شیء، تنظیم شده‌است.
سپس side effect خود را در قسمت effect function متد useEffect قرار می‌دهیم که آن نیز به متغیر handleMouseMove اشاره می‌کند:
  const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    // ...
    window.addEventListener("mousemove", handleMouseMove);
  });

  const handleMouseMove = event => {
    setMousePosition({
      x: event.pageX,
      y: event.pageY
    });
  };
به متغیر handleMouseMove، متدی انتساب داده شده‌است که با فراخوانی آن توسط mousemove listener، یک شیء evenet دریافت و سپس بر اساس خواص آن، خواص x و y شیء موجود در state، توسط متد setMousePosition، به روز رسانی می‌شود.
سپس برای نمایش x و y به روز رسانی شده‌ی در state، می‌توان از markup زیر در متد رندر استفاده کرد.
<h2>Mouse Position</h2>
{JSON.stringify(mousePosition, null, 2)}
<br />
اکنون سؤال اینجا است که معادل متد componentWillUnmount در اینجا چیست؟
اگر effect function تعریف شده، دارای یک خروجی (از نوع تابع) باشد، به این معنا است که این side effect، نیاز به پاکسازی دارد و این متد را در زمان Unmount آن فراخوانی می‌کند:
  useEffect(() => {
    // …
    // componentDidMount & componentDidUpdate
    window.addEventListener("mousemove", handleMouseMove);

    // componentWillUnmount
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  });
پیشتر آموختیم که effect function، تنها یکبار اجرا نمی‌شود؛ بلکه به ازای هر بار رندر، یکبار پس از آن اجرا می‌شود. یعنی خروجی آن نه فقط در زمان Unmount اجرا می‌شود، بلکه یکبار هم پیش از هر بار اجرای خود effect اجرا می‌گردد. به این ترتیب در اینجا فرصت پاکسازی اجرای قبلی را نیز خواهیم داد. بنابراین روش پاکسازی آن نسبت به متد componentWillUnmount کامپوننت‌های تابعی، پیشرفته‌تر است. یعنی توسط آن می‌توان یک side effect خاص را پیش و پس از هر بار رندر، در صورت نیاز فراخوانی کرد.

سؤال: اگر بخواهیم از اجرای یک side effect، به ازای هر بار رندر جلوگیری کنیم، چه باید کرد؟

برای اینکار می‌توان آرگومان دومی را به متد useEffect اضافه کرد که آرایه‌ای از مقادیر است. توسط اعضای آن می‌توان مقدار و یا مقادیری را مشخص کرد که side effect تعریف شده، به آن وابسته‌است. اکنون اگر این مقدار تغییر کند، آنگاه side effect متناظر با آن نیز اجرا می‌شود:
  useEffect(() => {
    document.title = `You have clicked ${count} times`;
    window.addEventListener("mousemove", handleMouseMove);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  },[]);
در این مثال، چون پارامتر دوم را به صورت یک آرایه‌ی خالی مقدار دهی کرده‌ایم، effect function تعریف شده تنها در زمان mount و unmount اجرا می‌شود. البته اگر این تغییر را ذخیره کرده و برنامه را اجرا کنیم، تمام قسمت‌های تعریف شده‌ی آن به خوبی کار می‌کنند (با کلیک بر روی دکمه، برچسب آن به روز رسانی می‌شود و یا با حرکت کرسر ماوس در صفحه، موقعیت آن گزارش داده می‌شود)، منهای قسمت به روز رسانی عنوان صفحه با هر بار کلیک. علت اینجا است که با تغییر معرفی شده، دیگر حالت componentDidUpdate توسط متد useEffect پوشش داده نمی‌شود و به این ترتیب با هر بار به روز رسانی state و رندر کامپوننت، دیگر کار به اجرای مجدد side effect آن نمی‌رسد.
برای رفع این مشکل، باید به useEffect اعلام کنیم که side effect تعریف شده‌ی در آن، وابسته‌است به مقدار count و با تغییرات آن در state، نیاز است مجددا اجرا شود:
  useEffect(() => {
   // ...
  },[count]);
به همین جهت در آرایه‌ی تعریف شده، مقداری را که به آن وابستگی وجود دارد، مشخص می‌کنیم. با این تغییر اگر بر روی دکمه کلیک کنیم، چون اکنون useEffect می‌داند که باید تغییرات count را ردیابی کند، با اجرای مجدد effect function خود، عنوان صفحه نیز به روز رسانی خواهد شد.


کار با چندین listener مختلف در متد useEffect

سؤال: آیا تنظیم یک وابستگی خاص در متد useEffect، امکان تنظیم event listenerهای دیگر را غیرممکن می‌کند؟
برای پاسخ به این سؤال، چند event listener دیگر را ثبت می‌کنیم. برای مثال یکی دیگر از APIهای مرورگر، navigator نام دارد که توسط آن می‌توان وضعیت آنلاین و آفلاین بودن را به کمک خروجی خاصیت navigator.onLine آن، مشخص کرد. به کمک این API می‌خواهیم این وضعیت را نمایش دهیم. برای این منظور ابتدا state آن‌را در کامپوننت تابعی، ایجاد می‌کنیم:
const [status, setStatus] = useState(navigator.onLine);
مقدار اولیه‌ی این state را نیز توسط مقدار جاری خاصیت navigator.onLine، مشخص کرده‌ایم.
اکنون برای گوش فرا دادن به تغییرات این خاصیت (online و یا offline شدن کاربر)، نیاز است دو event listener را به کمک متد addEventListener ثبت کنیم و همچنین این متدها نیاز به پاکسازی هم دارند:
  useEffect(() => {
    // ...

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    return () => {
      // ...
  
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, [count]);
پس از ثبت event listener ها، اکنون می‌توان با هربار فراخوانی آن‌ها توسط مرورگر، وضعیت state آن‌را به true و یا false، تغییر داد:
  const handleOnline = () => {
    setStatus(true);
  };

  const handleOffline = () => {
    setStatus(false);
  };
و در آخر مقدار متغیر status را به نحو زیر در متد رندر، نمایش داد:
<h2>Network Status</h2>
<p>
  You are <strong>{status ? "online" : "offline"}</strong>
</p>

برای آزمایش حالت offline آن، فقط کافی است به ابزار توسعه دهندگان مرورگر مراجعه کرده و در برگه‌ی network آن، حالت online را offline کنید:


در این حالت هم نمایش وضعیت آنلاین بودن کاربر به درستی کار می‌کند و هم سایر قسمت‌هایی که تاکنون اضافه کرده‌ایم. به این معنا که هر چند توسط ذکر پارامتر [count]، وابستگی خاصی برای side effect ویژه‌ای، مشخص شده‌است، اما ما را از تنظیم event listener‌های دیگری در قسمت‌های mount و unmount محروم نمی‌کند.


پاکسازی اثرات جانبی در useEffect Hook، زمانیکه روشی برای آن وجود ندارد

در مثالی دیگر می‌خواهیم از API موقعیت جغرافیایی کاربر در مرورگر یا navigator.geolocation استفاده کنیم. توسط این API هم می‌توان طول و عرض جغرافیایی را به دست آورد و هم تغییرات آن‌را تحت نظر قرار داد. برای مثال با بررسی این تغییرات می‌توان سرعت را نیز به دست آورد.
در این حالت نیز ابتدا با تعریف state مختص به آن شروع می‌کنیم و اینبار به عنوان مثال، مقدار اولیه‌ی آن‌را خارج از تابع جاری تنظیم می‌کنیم (جهت نمایش یک گزینه‌ی مهیای دیگر):
const initialLocationState = {
  latitude: null,
  longitude: null,
  speed: null
};

const App = () => {
  // ...
  const [location, setLocation] = useState(initialLocationState);
سپس جهت کار با این API، مقدار اولیه‌ی موقعیت کاربر و همچنین ردیابی تغییرات آن‌را در متد useEffect، تنظیم می‌کنیم:
  useEffect(() => {
    // ...

    navigator.geolocation.getCurrentPosition(handleGeolocation);
    const watchId = navigator.geolocation.watchPosition(handleGeolocation);

    return () => {
      // ...
      navigator.geolocation.clearWatch(watchId);
    };
  }, [count]);
در اینجا متد getCurrentPosition، با متد addEventListener متفاوت است و تنها یک callback function را دریافت می‌کند که در آن می‌توان setLocation را جهت به روز رسانی موقعیت جاری جغرافیایی کاربر، فراخوانی کرد. مشکلی که با این متد وجود دارد، نداشتن معادلی شبیه به متد removeEventListener است. یعنی API آن روشی را برای unmount آن فراهم نکرده‌است. البته متد watchPosition که تغییرات موقعیت کاربر را ردیابی می‌کند، دارای API مخصوص پاکسازی آن نیز هست که در این کدها ذکر شده‌است.

یک روش برای حل این مشکل و غیرفعال کردن دستی listener متد getCurrentPosition پس از unmount، تعریف یک متغیر mounted پیش از متد useEffect است:
  let mounted = true;

  useEffect(() => {
   // ...

    return () => {
      // ...
      mounted = false;
    };
  }, [count]);
و آن‌را در زمان پاکسازی useEffect، با false مقدار دهی می‌کنیم. اکنون زمانیکه می‌خواهیم رویدادگردان این Listener را تعریف کنیم، تنها بر اساس مقدار متغیر mounted عمل خواهیم کرد که از نشتی حافظه‌ی آن جلوگیری می‌کند:
  const handleGeolocation = event => {
    if (mounted) {
      setLocation({
        latitude: event.coords.latitude,
        longitude: event.coords.longitude,
        speed: event.coords.speed
      });
    }
  };
در آخر هم برای نمایش آن در متد رندر به صورت زیر عمل می‌کنیم:
<h2>Geolocation</h2>
<p>Latitude is {location.latitude}</p>
<p>Longitude is {location.longitude}</p>
<p>Your speed is {location.speed ? location.speed : "0"}</p>

سؤال: در اینجا شیء location چندین بار تکرار شده‌است. آیا می‌توان مقادیر خواص آن‌را توسط Object Destructuring نیز به دست آورد؟
پاسخ: بله. در اینجا هم Object Destructuring بر روی location state، کار می‌کند:
const [{ latitude, longitude, speed }, setLocation] = useState(
    initialLocationState
  );
که پس از آن، دیگر نیازی به تکرار شیء location نخواهد بود:
<h2>Geolocation</h2>
<p>Latitude is {latitude}</p>
<p>Longitude is {longitude}</p>
<p>Your speed is {speed ? speed : "0"}</p>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-01.zip
نظرات مطالب
EF Code First #10
- بله. همینطوره. به همین جهت یک قسمت در کد فوق کامنت شده نوشته شده:
// note: remove this line if you received : CreateDatabase is not supported by the provider.
EF Prof با سیستم به روز رسانی بانک اطلاعاتی EF Code first تداخل ایجاد می‌کنه. اول دیتابیس رو به روز کنید. بعد تنظیمات EF Prof رو اضافه کنید و بعد هم آغاز دیتابیس رو با null مقدار دهی کنید.

- ضمنا گروه مرتبط با EF Prof و محصولات مشابه اینجا است:
http://groups.google.com/group/efprof 
بهتر است این نوع مشکلات را با خودشون مطرح کنید.
بازخوردهای دوره
ارتباطات بلادرنگ و SignalR
- کار ما در اینجا پشتیبانی مقالات سایت‌های دیگر نیست. ضمنا در قسمت اول، یک مبحث کاملا خارج از موضوع را نباید ارسال کنید. در ذیل هر مطلب عنوان شده برای این نوع موارد مرتبط به دوره، لطفا به قسمت پرسش و پاسخ دوره مراجعه کنید.
- من از جزئیات کار شما اطلاعی ندارم. نه خطایی را عنوان کردید و نه پروژه‌ای برای دیباگ پیوست شده. ایشان عنوان کرده که اجرا می‌شود؛ یک فیلم هم پیوست کرده. ضمنا با jQuery Ajax کار کرده قسمتی را. یعنی یک سری پیشنیاز دیگر را هم باید به پروژه و صفحه اضافه کنید. در کل از راه دور و بدون دیدن کار شما نمی‌شود نظر داد (کل کار البته).
- در آن مقاله سایت ثالث، dependency_OnChange فقط زمانی رجیستر می‌شود که GetData یکبار فراخوانی شود. ضمنا این کد نشتی حافظه دارد. چون مدام دارد new OnChangeEventHandler را ایجاد می‌کند بدون اینکه فکری برای حذف موارد ثبت شده کند. همچنین JobInfoRepository را در سطح یک Web API Controller وهله سازی کرده. یعنی این وهله به ازای هر درخواست رسیده یکبار ایجاد می‌شود (ونه اینکه یکبار ایجاد شده و بارها استفاده شود) و به این ترتیب یکبار دیگر نیز OnChangeEventHandler رجیستر خواهد شد. خلاصه اینکه روش مناسبی نبوده و توصیه نمی‌شود.

مطلبت حذف شد. تکرار کنی خودت هم حذف می‌شی. شک نداشته باش.
نظرات نظرسنجی‌ها
کدام یک از فریمورک‌های زیر را جهت تولید نرم افزارهای وب پایه SPA مناسب می‌دانید؟
هر چند تجربه کار با این کتابخانه‌ها بیشتر برای من در حد مشاهده بوده ولی به نظر من به چند دلیل انگیولار برتری داره
اول اینکه مستندات و مثال‌ها و کدهای زیادی ازش در اینترنت یافت میشه
دوم اینکه برنامه نویس‌های بیشتری در این کتابخانه در ایران هستند که در صورت پروژه ای که قبلا با انگیولار نوشته شده باشه و نیاز به توسعه در آینده داشته باشه و اگه تیم سابق نباشه میشه افرادی جدیدتری رو سریع و راحت‌تر پیدا کرد.
سوم اینکه انگیولار توسط گوگل منتشر میشه که نسبت به بقیه معتبرتره و هم اینکه چون بازنویسی کردن احتمال مشکل در آینده براش به شدت پایین میاد.
فکر میکنم اون هشت گزینه ای که شما مطح کردید رو انگیولار به خوبی داره.
مطالب
نحوه استفاده از افزونه Firebug برای دیباگ برنامه‌های ASP.NET مبتنی بر jQuery
هر از چندگاهی سؤال «این مثال jQuery رو نمی‌تونم اجرا یا باز سازی کنم» در این سایت یا سایت‌های مشابه تکرار می‌شوند. بنابراین بهتر است نحوه عیب یابی برنامه‌های ASP.NET مبتنی بر jQuery را یکبار با هم مرور کنیم. در اینجا، مثال تهیه یک Image Slider را که پیشتر در سایت مطرح شده است، به نحوی دیگر بررسی خواهیم کرد:
1) فراموش می‌کنیم تا اسکریپت اصلی jQuery را به درستی پیوست و مسیردهی کنیم.
2) مسیر Generic handler دیگری را ذکر می‌کنیم.
3) مسیرهای تصاویری را که Image slider باید نمایش دهد، کاملا بی‌ربط ذکر می‌کنیم.
4) خروجی JSON نامربوطی را بازگشت می‌دهیم.
5) یکبار هم یک استثنای عمدی دستی را در بین کدها قرار خواهیم داد.

و ... بعد سعی می‌کنیم با استفاده از Firebug عیوب فوق را یافته و اصلاح کنیم؛ تا به یک برنامه قابل اجرا برسیم.


معرفی برنامه‌ای که کار نمی‌کند!

یک برنامه ASP.NET Empty web application را آغاز کنید. سپس سه پوشه Scripts، Content و Images را به آن اضافه نمائید. در این پوشه‌ها، اسکریپت‌های نمایش دهنده تصاویر، Css آن و تصاویری که قرار است نمایش داده شوند، قرار می‌گیرند:


سپس یک فایل default.aspx و یک فایل OrbitHandler.ashx را نیز به پروژه با محتویات ذیل اضافه کنید: (در این دو فایل، 5 مورد مشکل ساز یاد شده لحاظ شده‌اند)
محتویات فایل OrbitHandler.ashx.cs مطابق کدهای ذیل است:
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.Script.Serialization;

namespace OrbitWebformsTest
{
    public class Picture
    {
        public string Title { set; get; }
        public string Path { set; get; }
    }

    public class OrbitHandler : IHttpHandler
    {
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = /*"Images/" + name*/ name,
                    Title = name
                });
            }

            return results;
        }

        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = /*new JavaScriptSerializer().Serialize(items)*/ string.Empty;
            throw new InvalidDataException("همینطوری");
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }

        public bool IsReusable
        { 
            get { return false; } 
        }
    }
}
در اینجا جهت سهولت دموی برنامه (و همچنین امکان باز تولید آن توسط خوانندگان)، از بانک اطلاعاتی استفاده نشده و عمدا از یک لیست جنریک تشکیل شده در حافظه کمک گرفته شده است. تصاویر برنامه در پوشه Images واقع در ریشه سایت، قرار دارند. بنابراین توسط متد PicturesDataSource، فایل‌های این پوشه را یافته و مطابق ساختار کلاس Picture بازگشت می‌دهیم. نهایتا این اطلاعات به ظاهر قرار است با فرمت JSON بازگشت داده شوند تا بتوان نتیجه را توسط افزونه Orbit استفاده کرد.

همچنین کدهای صفحه ASPX ایی که قرار است (به ظاهر البته) از این Generic handler استفاده کند به نحو ذیل است:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="OrbitWebformsTest._default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="Content/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.Path + '" alt="' + b.Title + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"
            });
        });
    </script>
</body>
</html>
خوب! اگر پروژه را اجرا کنیم، کار نمی‌کند. یک مستطیل مشکی رنگ در کنار صفحه ظاهر شده و همین! حالا چکار باید کرد؟


مراحل عیب یابی برنامه‌ای که کار نمی‌کند!

ابتدا برنامه را در فایرفاکس باز کرده و سپس افزونه Firebug را با کلیک بر روی آیکن آن، بر روی سایت فعال می‌کنیم. سپس یکبار بر روی دکمه F5 کلیک کنید تا مجددا مراحل بارگذاری سایت تحت نظر افزونه Firebug فعال شده، طی شود.


اولین موردی که مشهود است، نمایش عدد 3، کنار آیکن فایرباگ می‌باشد. این عدد به معنای وجود خطاهای اسکریپتی در کدهای ما است.
برای مشاهده این خطاها، بر روی برگه Console آن کلیک کنید: 


بله. مشخص است که مسیر دهی فایل jquery-1.5.1.min.js صحیح نبوده و همین مساله سبب بروز خطاهای اسکریپتی گردیده است. برای اصلاح آن سطر زیر را در برنامه تغییر دهید:
 <script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
پیشتر پوشه Script ذکر شده بود که باید تبدیل به Scripts شود.

مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید. اینبار در برگه Console و یا در برگه شبکه فایرباگ، خطای یافت نشدن Generic handler نمایان می‌شوند:


برای رفع آن به فایل default.aspx مراجعه و بجای معرفی Handler.ashx، نام OrbitHandler.ashx را وارد کنید.
مجددا دکمه F5 را فشرده و سایت را با تنظیمات جدید اجرا کنید.


اگر به برگه کنسول دقت کنیم، بروز استثناء در کدها تشخیص داده شده و همچنین در برگه Response پاسخ دریافتی از سرور، جزئیات صفحه خطای بازگشتی از آن نیز قابل بررسی و مشاهده است.
اینبار به فایل OrbitHandler.ashx.cs مراجعه کرده و سطر throw new InvalidDataException را حذف می‌کنیم. در ادامه برنامه را کامپایل و مجددا اجرا خواهیم کرد.



با اجرای مجدد سایت، تبادل اطلاعات صحیحی با فایل OrbitHandler.ashx برقرار شده است، اما خروجی خاصی قابل مشاهده نیست. بنابراین بازهم سایت کار نمی‌کند.
برای رفع این مشکل، متد ProcessRequest را به نحو ذیل تغییر خواهیم داد:
        public void ProcessRequest(HttpContext context)
        {
            var items = PicturesDataSource();
            var json = new JavaScriptSerializer().Serialize(items);            
            context.Response.ContentType = "text/plain";
            context.Response.Write(json);
        }
برنامه را کامپایل کرده و اجرا می‌کنیم. برنامه اجرا می‌شود، اما باز هم کار نمی‌کند. مشکل از کجاست؟


بله. تمام تنظیمات به نظر درست هستند، اما در برگه شبکه فایرباگ تعدادی خطای 404 و یا «یافت نشد»، مشاهده می‌شوند. مشکل اینجا است که مسیرهای بازگشت داده شده توسط متد Directory.GetFiles، مسیرهای مطلقی هستند؛ مانند c:\path\images\01.jpg و جهت نمایش در یک وب سایت مناسب نمی‌باشند. برای تبدیل آن‌ها به مسیرهای نسبی، اینبار کدهای متد تهیه منبع داده را به نحو ذیل ویرایش می‌کنیم:
        IList<Picture> PicturesDataSource()
        {
            var results = new List<Picture>();
            var path = HttpContext.Current.Server.MapPath("~/Images");

            foreach (var item in Directory.GetFiles(path, "*.*"))
            {
                var name = Path.GetFileName(item);
                results.Add(new Picture
                {
                    Path = "Images/" + name,
                    Title = name
                });
            }

            return results;
        }
در این کدها فقط قسمت Path ویرایش شده است تا به مسیر پوشه Images واقع در ریشه سایت اشاره کند.
اینبار اگر برنامه را اجرا کنیم، بدون مشکل کار خواهد کرد.

بنابراین در اینجا مشاهده کردیم که اگر «برنامه‌ای مبتنی بر jQuery کار نمی‌کند»، چگونه باید قدم به قدم با استفاده از فایرباگ و امکانات آن، به خطاهایی که گزارش می‌دهد و یا مسیرهایی را که یافت نشد بیان می‌کند، دقت کرد تا بتوان برنامه را عیب یابی نمود.


سؤال مهم: اجرای کدهای jQuery Ajax فوق، چه تغییری را در صفحه سبب می‌شوند؟

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


در اینجا همانند VS.NET می‌توان برنامه را در مرورگر اجرا کرده و تگ‌های تصویر پویای تولید شده را پیش از اضافه شدن به صفحه، مرحله به مرحله بررسی کرد. به این ترتیب بهتر می‌توان دریافت که آیا src بازگشت داده شده از سرور فرمت صحیحی دارد یا خیر و آیا به محل مناسبی اشاره می‌کند یا نه. همچنین در برگه HTML آن، عناصر پویای اضافه شده به صفحه نیز بهتر مشخص هستند:

مطالب
آشنایی با CLR: قسمت بیستم
در قسمت قبلی با نحوه انتشار برنامه‌ها آشنا شدیم. در این قسمت نحوه پیکربندی یا تغییر پیکربندی برنامه را مشخص می‌کنیم.
کاربر یا مدیر سیستم بهتر از هر کسی می‌تواند جنبه‌های‌های مختلف اجرای برنامه را مشخص کند. به عنوان نمونه ممکن است مدیر سیستم بخواهد فایل‌های یک برنامه را سمت هارد دیسک سیستم کاربر انتقال دهد یا اطلاعات مانیفست یک اسمبلی را رونویسی کند و مباحث نسخه بندی که در آینده در مورد آن صحبت می‌کنیم.
با ارائه یک فایل پیکربندی در شاخه برنامه می‌توان به مدیر سسیتم  اجازه داد تا کنترل بیشتر بر روی برنامه داشته باشد. ناشر برنامه می‌تواند این فایل را همراه دیگر فایل‌های برنامه پکیج کند تا در شاخه برنامه نصب شود تا بعدا مدیر یا کاربر سیستم بتوانند آن را تغییر و ویرایش کنند. CLR هم محتوای این فایل را تفسیر کرده و قوانین بارگیری اسمبلی‌ها و ... را تغییر می‌دهد. این فایل پیکربندی می‌تواند به صورت XML هم ارائه شود. مزیت قرار دادن یک فایل جداگانه نسبت به رجیستری این مزیت را دارد که هم قابل جابجایی و پشتیبانی گیری است و هم اینکه تغییر آن ساده‌تر است.
البته در آینده بیشتر در مورد این فایل صحبت می‌کنیم ولی در حال حاضر بهتر است اندکی طعم آن را بچشیم. فرض را بر این می‌گذاریم که ناشر می‌خواهد فایل‌های اسمبلی MultiFileLibrary را در دایرکتوری جداگانه‌ای قرار دهد و چیزی شبیه به ساختار زیر را در نظر دارد:
AppDir directory (contains the application’s assembly files)
Program.exe
Program.exe.config (discussed below)


AuxFiles subdirectory (contains MultiFileLibrary’s assembly files)
MultiFileLibrary.dll
FUT.netmodule
RUT.netmodule

حال با تنظیم بالا به دلیل اینکه CLR انتظار دارد این اسمبلی را در دایرکتوری برنامه بیابد و با این جابجایی قادر به انجام این کار نیست، استثنای زیر را صادر می‌کند:
System.IO.FileNotFoundException
برای حل این مشکل، ناشر یک فایل XML را ایجاد کرده و در مسیر دایرکتوری برنامه قرار می‌دهد. این فایل باید همنام اسمبلی اصلی برنامه با پسوند config. باشد. به عنوان مثال، نام فایل می‌شود: Program.exe.config و فایل پیکربندی هم چیزی شبیه فایل زیر می‌شود:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas­microsoft­com:asm.v1">
<probing privatePath="AuxFiles" />
</assemblyBinding>
</runtime>
</configuration>
حالا هر موقع CLR در جست و جوی یک اسمبلی باشد که نتواند آن را در دایرکتوری مربوطه بیابد مسیر Auxfiles را هم بررسی خواهد کرد. با قرار دادن کارکتر « , » هم می‌توان برای خصوصیت PrivatePath، دایرکتوری‌های زیادتری را معرفی کرد. البته مسیردهی این خصوصیت باید به طور نسبی باشد نه مطلق یا اینکه یک مسیر نسبی خارج از دایرکتوری برنامه. ایده اصلی اینکار این است که برنامه کنترل بیشتری روی دایرکتوری خود و دایرکتوری‌های زیرمجموعه داشته باشد نه خارج از آن.

نحوه عملکرد اسکن CLR برای بارگزاری اسمبلی ها
موقعی که CLR قصد بارگزاری اسمبلی‌های خنثی را دارد، شاخه‌های زیر را به طور خودکار اسکن خواهد نمود ( مسیرهای FirstPrivatePath و SecondPrivatePath توسط فایل پیکربندی مشخص شده است)
AppDir\AsmName.dll
AppDir\AsmName\AsmName.dll
AppDir\firstPrivatePath\AsmName.dll
AppDir\firstPrivatePath\AsmName\AsmName.dll
AppDir\secondPrivatePath\AsmName.dll
AppDir\secondPrivatePath\AsmName\AsmName.dll
...

این نکته ضروری است که اگر اسمبلی شما در یک دایرکتوری همنام خودش (در مثال ما MultiFileLibrary) قرار بگیرد، نیازی نیست این مسیر را در فایل پیکربندی ذکر کنید؛ زیرا CLR در صورت نیافتن دایرکتوری با این نام را اسکن خواهد نمود. بعد از آن اگر به هر نحوی CLR نتواند اسمبلی را در هیچ کدام از دایرکتوری‌های گفته شده بیابد، با همان قوانین گفته شده اینبار به دنبال فایلی با پسوند exe خواهد بود و اگر باز هم جست و جوی آن نتیجه‌ای را در بر نداشته باشد، استثنای زیر را صادر می‌کند:
 FileNotFoundException
برای اسمبلی‌های ماهواره‌ای همان قوانین بالا دنبال می‌شود؛ با این تفاوت که انتظار می‌رود اسمبلی داخل یک زیر دایرکتوری با تگ‌های RFC1766 مطابقت داشته باشد. به عنوان مثال اگر اسمبلی با فرهنگ و منطقه En-US مشخص شده باشد، دایرکتوری‌های زیر اسکن خواهند شد:
C:\AppDir\en­US\AsmName.dll
C:\AppDir\en­US\AsmName\AsmName.dll
C:\AppDir\firstPrivatePath\en­US\AsmName.dll
C:\AppDir\firstPrivatePath\en­US\AsmName\AsmName.dll
C:\AppDir\secondPrivatePath\en­US\AsmName.dll
C:\AppDir\secondPrivatePath\en­US\AsmName\AsmName.dll
C:\AppDir\en­US\AsmName.exe
C:\AppDir\en­US\AsmName\AsmName.exe
C:\AppDir\firstPrivatePath\en­US\AsmName.exe
C:\AppDir\firstPrivatePath\en­US\AsmName\AsmName.exe
C:\AppDir\secondPrivatePath\en­US\AsmName.exe
C:\AppDir\secondPrivatePath\en­US\AsmName\AsmName.exe
C:\AppDir\en\AsmName.dll
C:\AppDir\en\AsmName\AsmName.dll
C:\AppDir\firstPrivatePath\en\AsmName.dll
C:\AppDir\firstPrivatePath\en\AsmName\AsmName.dll
C:\AppDir\secondPrivatePath\en\AsmName.dll
C:\AppDir\secondPrivatePath\en\AsmName\AsmName.dll
C:\AppDir\en\AsmName.exe
C:\AppDir\en\AsmName\AsmName.exe
C:\AppDir\firstPrivatePath\en\AsmName.exe
C:\AppDir\firstPrivatePath\en\AsmName\AsmName.exe
C:\AppDir\secondPrivatePath\en\AsmName.exe
C:\AppDir\secondPrivatePath\en\AsmName\AsmName.exe

نحوه اسکن کردن CLR میتواند به ما بگوید که عمل اسکن می‌تواند گاهی اوقات با زمان زیادی روبرو شود (به خصوص که قابلیت اسکن روی شبکه را هم دارد). برای محدود کردن ناحیه یا نواحی اسکن می‌توانید یک یا چند المان culture را در فایل پیکربندی مشخص کنید. همچنین مایکروسافت ابزاری به نام FUSLogVw.exe را ارائه داده است که می‌تواند نواحی اسکن را در حین اجرای برنامه به ما گزارش دهد.

نام و محل فایل پیکربندی بسته به نوع برنامه می‌تواند متغیر باشد:
برای فایل‌های اجرایی EXE فایل پیکربندی باید در شاخه فایل اجرایی باشد و باید نام فایل پیکربندی همانند فایل exe بوده و یک پسوند config. را به آن اضافه کرد.

برای برنامه‌های وب فرم، فایل web.config موجود است که در ریشه شاخه مجازی وب اپلیکیشن قرار میگیرد و هر زیر دایرکتوری هم می‌تواند یک web.config جداگانه داشته باشد که میتواند از web.config ریشه هم تنظمیاتش را ارث بری کند. برای نمونه آدرس زیر را در نظر بگیرد:
https://www.dntips.ir/newsarchive

یک فایل کانفیگ در ریشه قرار می‌گیرد و یکی هم در زیر شاخه newsArchive  می‌تواند قرار بگیرد.

فصل دوم «نحوه ساخت و توزیع اسمبلی ها» از بخش اول «اصول اولیه CLR» پایان یافت. فصل بعدی در مورد اسمبلی‌های اشتراکی است که بعد از آماده شدن این فصل، قسمت‌های بعدی در دسترس عزیزان قرار خواهد گرفت.