مطالب
تنظیمات پیشنهادی حداکثر حافظه‌ی مصرفی اس کیوال سرور

به صورت پیش فرض تنظیمات حافظه‌ی اس کیوال سرور به صورتی است که به آن اجازه می‌دهد تمامی حافظه‌ی مهیای سیستم عامل را مصرف کند! اگر شخصی با این مساله آشنایی نداشته باشد احتمالا تصور خواهد کرد که اس کیوال سرور نشتی حافظه دارد یا کلا مشکلی در سیستم روی داده است که تا این حد مصرف حافظه بالا رفته است.
برای مشاهده‌ی این تنظیمات روی instance سرور مورد نظر کلیک راست کرده و گزینه خواص را انتخاب کنید. سپس در صفحه‌ی باز شده گزینه memory را مشاهده نمائید.
البته این‌کار را با استفاده از دستورات T-SQL هم می‌توان انجام داد:

EXEC sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE

EXEC sp_configure 'max server memory (MB)'

EXEC sp_configure 'show advanced options', 0
RECONFIGURE WITH OVERRIDE

نتیجه‌ی آن بر روی سیستم برنامه نویسی من به صورت زیر است:



که البته عمدا آن‌را بر روی یک گیگ تنظیم کرده‌ام تا عملکرد سیستم مختل نشود. (چون اگر غیر از این باشد، تعارفی نداشته و هر آنچه را که صلاح بداند مصرف می‌کند!)
کمی در بلاگ‌های رسمی برنامه نویس‌های اس کیوال سرور در سایت MSDN در این‌‌باره جستجو کردم و نتیجه آن به صورت زیر است:

تنظیمات حداکثر حافظه‌ی مصرفی اس کیوال سرورهای 2005 و 2008
برای یک سیستم عامل 64 بیتی:

Physical RAM MaxMem Setting
2GB 1500
4GB 3200
6GB 4800
8GB 6700
12GB 10600
16GB 14500
24GB 22400
32GB 30000
48GB 45000
64GB 59000


برای سروری 32 بیتی این اعداد حداکثر را تقریبا منهای 300 مگابایت نمائید. (و البته همانطور که مطلع هستید در سیستم عامل‌های سرور 32 بیتی، اگر بیشتر از 2 گیگا بایت رم داشتید سوئیچ 3GB و اگر بیشتر از 4 گیگابایت رم مهیا بود، سوئیچ PAE باید در boot.ini تنظیم شوند. در غیر اینصورت هزینه‌ی سخت افزاری بیهوده‌ای را متحمل شده‌اید، زیرا استفاده‌ی بهینه‌ای از آن در یک سیستم عامل 32 بیتی نخواهد شد)

و این اعداد را همانطور که ذکر شد، در تنظیمات سرور می‌توان وارد نمود ( از طریق management studio ) و یا با استفاده از دستورات T-SQL نیز این امر میسر است: (در مثال زیر حداکثر حافظه مجاز مصرفی به 2300 مگابایت تنظیم می‌شود)

EXEC sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE

EXEC sp_configure 'max server memory (MB)', 2300

EXEC sp_configure 'show advanced options', 0
RECONFIGURE WITH OVERRIDE

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


برای مطالعه‌ی بیشتر
http://sqlnerd.blogspot.com/2006/07/memory-use-in-sql-server.html
http://blogs.msdn.com/axperf/archive/2008/03/10/welcome-database-configuration-checklist-part-1.aspx
http://technet.microsoft.com/en-us/library/bb124810.aspx

نظرات مطالب
آشنایی با NHibernate - قسمت هفتم
بحث entity framework با NHibernate متفاوت است.
در NHibernate این متد BuildSessionFactory فوق کار بارگذاری متادیتا و نگاشت‌ها و غیره رو انجام میده؛ یعنی خودکار نیست و اگر قرار باشه به ازای هر کوئری یکبار فراخوانی شود اصلا نمی‌شود با برنامه کار کرد چون به شدت کند خواهد بود. به همین جهت کش کردن آن‌را با استفاده از الگوی singleton به صورت فوق تنها یکبار باید انجام داد. یکبار در طول عمر برنامه باید نگاشت‌ها صورت گیرد و پس از آن بارها و بارها از آن استفاده شود چون قرار نیست در طول عمر یک برنامه در حال اجرا تغییری کند.
در حالیکه در Entity framework اینکار (بارگذاری متادیتا و نگاشت‌های تعریف شده) به صورت خودکار زمانیکه برنامه برای بار اول اجرا می‌شود رخ داده و به صورت خودکار هم کش می‌شود. ماخذ: (+) ؛ بنابراین برای مدیریت آن اصلا لازم نیست شما کاری انجام دهید.
فقط در Entity framework یک لایه بسیار کم هزینه به نام ObjectContext وجود دارد که توصیه شده در برنامه‌های ASP.NET به ازای هر درخواست ایجاد و تخریب شود که می‌توانید از مقاله فوق ایده بگیرید و اصلا نباید کش شود یا هر بحث دیگری. ماخذ: (+) ؛ حتی اگر اینکار را هم انجام ندادید مهم نیست چون سربار بسیار کمی دارد.
مطالب
استفاده از Awesomium.NET در برنامه‌های وب
برای تهیه تصاویر سایت‌های معرفی شده در قسمت اشتراک‌های سایت، پیشتر از کنترل WebBrowser دات نت که در پشت صحنه از امکانات IE کمک می‌گیرد، استفاده می‌کردم. بسیار ناپایدار است؛ به روز رسانی مشکلی داشته و وابسته است به سیستم عامل جاری سیستم. برای مثال مرتبا برای تهیه تصاویر بند انگشتی (Thumbnails) سایت‌های تهیه شده با بوت استرپ کرش می‌کرد و این کرش چون از نوع unmaged code است، عملا پروسه IIS وب سایت را از کار می‌انداخت و در این حالت سایت تا ری‌استارت بعدی IIS در دسترس نبود. برای حل این مشکل و بهبود کیفیت تصاویر تهیه شده، از پروژه Awesomium که در حقیقت مرورگر کروم را جهت استفاده در انواع و اقسام زبان‌های برنامه نویسی محصور می‌کند، کمک گرفته شد؛ که شرح آن‌را در ادامه ملاحظه خواهید کرد.


دریافت و نصب Awesomium

پروژه Awesomium دارای یک SDK است که از اینجا قابل دریافت می‌باشد. بعد از نصب آن در مسیر Awesomium SDK\1.7.3.0\wrappers\Awesomium.NET\Assemblies\Packed می‌توانید محصور کننده‌ی دات نتی آن‌را مشاهده کنید. منظور از Packed در اینجا، استفاده از DLLهای فشرده شده‌ی native آن است که در مسیر Awesomium SDK\1.7.3.0\build\bin\packed کپی شده‌اند. بنابراین برای توزیع این نوع برنامه‌ها نیاز است اسمبلی دات نتی Awesomium.Core.dll به همراه دو فایل بومی icudt.dll و awesomium.dll ارائه شوند.


تهیه تصاویر سایت‌ها به کمک Awesomium.NET

پس از نصب Awesomium اگر به مسیر Documents\Awesomium SDK Samples\1.7.3.0\Awesomium.NET\Samples\Core\CSharp\BasicSample مراجعه کنید، مثالی را در مورد تهیه تصاویر سایت‌ها به کمک Awesomium.NET، مشاهده خواهید کرد. خلاصه‌ی آن چند سطر ذیل است:
            try
            {
                using (WebSession mywebsession = WebCore.CreateWebSession(
new WebPreferences() { CustomCSS = "::-webkit-scrollbar { visibility: hidden; }" }))
                {
                    using (var view = WebCore.CreateWebView(1240, 1000, mywebsession))
                    {
                        view.Source = new Uri("https://site.com/");

                        bool finishedLoading = false;
                        view.LoadingFrameComplete += (s, e) =>
                        {
                            if (e.IsMainFrame)
                                finishedLoading = true;
                        };

                        while (!finishedLoading)
                        {
                            Thread.Sleep(100);
                            WebCore.Update();
                        }

                        using (var surface = (BitmapSurface)view.Surface)
                        {
                            surface.SaveToJPEG("result.jpg");
                        }
                    }
                }
            }
            finally
            {
                WebCore.Shutdown();
            }
کار با ایجاد یک WebSession شروع می‌شود. سپس با مقدار دهی CustomCSS، اسکرول بار صفحات را جهت تهیه تصاویری بهتر مخفی می‌کنیم. سپس یک WebView آغاز شده و منبع آن به Url مدنظر تنظیم می‌شود. در ادامه باید اندکی صبر کنیم تا بارگذاری سایت خاتمه یافته و نهایتا می‌توانیم سطح این View را به صورت یک تصویر ذخیره کنیم.


مشکل! این روش در برنامه‌های ASP.NET کار نمی‌کند!

مثال همراه آن یک مثال کنسول ویندوزی است و به خوبی کار می‌کند؛ اما در برنامه‌های وب پس از چند روز سعی و خطا مشخص شد که:
الف) WebCore.Shutdown فقط باید در پایان کار یک برنامه فراخوانی شود. یعنی اصلا نیازی نیست تا در برنامه‌های وب فراخوانی شود.
 System.InvalidOperationException: You are attempting to re-initialize the WebCore.
The WebCore must only be initialized once per process and must be shut down only when the process exits.
ب) Awesomium فقط در یک ترد کار می‌کند. به این معنا که اگر کدهای فوق را در یک صفحه‌ی وب فراخوانی کنید، بار اول کار خواهد کرد. بار دوم برنامه کرش می‌کند؛ با این پیغام خطا:
 System.AccessViolationException: Attempted to read or write protected memory.
This is often an indication that other memory is corrupt. at Awesomium.Core.NativeMethods.WebCore_CreateWebView_1(HandleRef jarg1, Int32 jarg2, Int32 jarg3, HandleRef jarg4)
چون هر صفحه‌ی وب در یک ترد مجزا اجرا می‌شود، عملا استفاده‌ی مستقیم از Awesomium در آن ممکن نیست.
خطای فوق هم از آن نوع خطاهایی است که پروسه‌ی IIS را درجا خاموش می‌کند.


استفاده از Awesomium در یک ترد پس زمینه

راه حلی که نهایتا پاسخ داد و به خوبی و پایدار کار می‌کند، شامل ایجاد یک ترد مجزای Awesomium در زمان آغاز برنامه‌ی وب و زنده نگه داشتن آن تا زمان پایان کار برنامه است.
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Web;
using Awesomium.Core;

namespace AwesomiumWebModule
{
    public class AwesomiumModule : IHttpModule
    {
        private static readonly Thread WorkerThread = new Thread(awesomiumWorker);
        private static readonly ConcurrentQueue<AwesomiumRequest> TaskQueue = new ConcurrentQueue<AwesomiumRequest>();
        private static bool _isRunning = true;

        static AwesomiumModule()
        {
            WorkerThread.Start();
        }       

        private static void awesomiumWorker()
        {
            while (_isRunning)
            {
                if (TaskQueue.Count != 0)
                {
                    AwesomiumRequest outRequest;
                    if (TaskQueue.TryDequeue(out outRequest))
                    {
                        var img = AwesomiumThumbnail.FetchWebPageThumbnail(outRequest);
                        File.WriteAllBytes(outRequest.SavePath, img);
                        Thread.Sleep(500);
                    }
                }
                Thread.Sleep(5);
            }
        }

        public void Dispose()
        {
            _isRunning = false;
            WebCore.Shutdown();
        }

        public void Init(HttpApplication context)
        {
            context.EndRequest += endRequest;
        }

        static void endRequest(object sender, EventArgs e)
        {
            var url = HttpContext.Current.Items[Constants.AwesomiumRequest] as AwesomiumRequest;
            if (url!=null)
            {
                TaskQueue.Enqueue(url);
            }
        }
    }
}
در اینجا اگر در برنامه‌های وب فرم، از طریق HttpContext.Current.Items.Add و یا در برنامه‌های MVC به کمک this.HttpContext.Items.Add یک آیتم جدید، با کلید Constants.AwesomiumRequest و از نوع کلاس AwesomiumRequest دریافت گردد، مقدار آن به یک ConcurrentQueue اضافه خواهد شد. این صف در یک ترد مجزا مدام در حال بررسی است. اگر مقداری به آن اضافه شده‌است، از صف خارج شده و پردازش خواهد شد.
نمونه‌ی استفاده از آن، در سمت یک برنامه‌ی وب نیز به صورت زیر است. ابتدا ماژول تهیه شده باید در برنامه ثبت شود:
<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <httpModules>
      <add name="AwesomiumWebModule" type="AwesomiumWebModule.AwesomiumModule"/>
    </httpModules>
  </system.web>

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false"/>
    <modules>
      <add name="AwesomiumWebModule" type="AwesomiumWebModule.AwesomiumModule"/>
    </modules>
  </system.webServer>
</configuration>
سپس باید تنها مدیریت  HttpContext.Current.Items در سمت برنامه صورت گیرد:
        protected void btnStart_Click(object sender, EventArgs e)
        {
            var host = new Uri(txtUrl.Text).Host;
            HttpContext.Current.Items.Add(Constants.AwesomiumRequest, new AwesomiumRequest
            {
                Url = txtUrl.Text,
                SavePath = Path.Combine(HttpRuntime.AppDomainAppPath, "App_Data\\Thumbnails\\" + host + ".jpg"),
                TempDir = Path.Combine(HttpRuntime.AppDomainAppPath, "App_Data\\Temp")
            });
            lblInfo.Text = "Please wait. Your request will be served shortly.";
        }
در اینجا کاربر درخواست خود را در صف قرار می‌دهد. پس از مدتی کار آن در WorkerThread ماژول تهیه شده انجام گردیده و تصویر نهایی تهیه می‌شود.
Url، آدرس وب سایتی است که می‌خواهید تصویر آن تهیه شود. SavePath مسیر کامل فایل jpg نهایی است که قرار است دریافت و ذخیره گردد. TempDir محل ذخیره سازی فایل‌های موقتی Awesomium است. Awesomium یک سری کوکی، تصاویر و فایل‌های هر سایت را به این ترتیب کش کرده و در دفعات بعدی سریعتر عمل می‌کند.

پروژه‌ی کامل آن‌را از اینجا می‌توانید دریافت کنید:
AwesomiumWebApplication_V1.0.zip
 
نظرات مطالب
چگونه کد قابل تست بنویسیم - قسمت اول
با سلام،

ابتدا از مقاله جذابتون تشکر می‌کنم.

سوالی ذهن بنده رو درگیر کرده :
طبق مقالات آموزش ام وی سی همین سایت بنده لایه سرویسی توی پروژه هام می‌سازم که کارش مشابه بیان شماست :
"لایه دستیابی به داده / لایه ماندگاری :  این کد میداند چگونه به منبع داده متصل شود و یک کارت خرید را بازگرداند و یا چگونه یک کارت را در منبع داده ذخیره نماید. "

احساس می‌کنم که جای لایه بیزینس توی پروژهام خالیه ، لایه ای که کار محاسبات ریاضی و سایر محاسبات عددی رو به عهده داشته باشه.
از یکی از اساتیدم هم شنیدم که پروژها رو بصورت زیر می‌سازند
لایه دیتا - لایه بیزینس - لایه سرویس - لایه UI

که به نظرم لایه دیتا عملیات CRUD رو به عهده داشته باشه و لایه بیزینس هم محسابات و کارای پیچیده رو انجام بده و لایه سرویس هم لایه ای است که متدهای لازم جهت دسترسی UI به متدهای مورد نیاز در لایه‌های دیتا و بیزینس رو فراهم می‌کنه.
مثلا ثبت یک خرید جدید که موجب اجرای متد Add در کلاس ProductService میشه که در این متد ، متد CalcCommission جهت محاسبه پورسانت‌ها اجرا میشه و سپس نتیجه دریافتیبه کمک متدهای مربوطه در لایه دیتا در دیتابیس ثبت میشه.

به نظر میاد این لایه بندی قشنگ‌تر باشه.
(کل لایه‌های DomainClassess و DatabaseContext و Services پروژه‌های من در لایه دیتا قرار می‌گیرن )

می‌خواستم نظر شما رو درباره لایه بندی بدونم ؟
(به دنبال بهترین روش لایه بندی می‌گردم ، یک استاندارد مطمئن)
مطالب
ASP.NET MVC #5

بررسی نحوه انتقال اطلاعات از یک کنترلر به View‌های مرتبط با آن

در ASP.NET Web forms در فایل code behind یک فرم مثلا می‌توان نوشت Label1.Text و سپس مقداری را به آن انتساب داد. اما اینجا به چه ترتیبی می‌توان شبیه به این نوع عملیات را انجام داد؟ با توجه به اینکه در کنترلر‌ها هیچ نوع ارجاع مستقیمی به اشیاء رابط کاربری وجود ندارد و این دو از هم مجزا شده‌اند.
در پاسخ به این سؤال، همان مثال ساده قسمت قبل را ادامه می‌دهیم. یک پروژه جدید خالی ایجاد شده است به همراه HomeController ایی که به آن اضافه کرده‌ایم. همچنین مطابق روشی که ذکر شد، View ایی به نام Index را نیز به آن اضافه کرده‌ایم. سپس برای ارسال اطلاعات از یک کنترلر به View از یکی از روش‌های زیر می‌توان استفاده کرد:

الف) استفاده از اشیاء پویا

ViewBag یک شیء dynamic است که در دات نت 4 امکان تعریف آن میسر شده است. به این معنا که هر نوع خاصیت دلخواهی را می‌توان به این شیء انتساب داد و سپس این اطلاعات در View نیز قابل دسترسی و استخراج خواهد بود. مثلا اگر در اینجا به شیء ViewBag، خاصیت دلخواه Country را اضافه کنیم و سپس مقداری را نیز به آن انتساب دهیم:

using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";
return View();
}
}
}

این اطلاعات در View مرتبط با اکشنی به نام Index به نحو زیر قابل بازیابی خواهد بود (نحوه اضافه کردن View متناظر با یک اکشن یا متد را هم در قسمت قبل با تصویر مرور کردیم):

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>
Country : @ViewBag.Country
</p>

در این مثال، @ در View engine جاری که Razor نام دارد، به این معنا می‌باشد که این مقداری است که می‌خواهم دریافت کنی (ViewBag.Country) و سپس آن‌را در حین پردازش صفحه نمایش دهی.


ب) انتقال اطلاعات یک شیء کامل و غیر پویا به View

هر پروژه جدید MVC به همراه پوشه‌ای به نام Models است که در آن می‌توان تعاریف اشیاء تجاری برنامه را قرار داد. در پروژه جاری، یک کلاس ساده را به نام Employee به این پوشه اضافه می‌کنیم:

namespace MvcApplication1.Models
{
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}

اکنون برای نمونه یک وهله از این شیء را در متد Index ایجاد کرده و سپس به view متناظر با آن ارسال می‌کنیم (در قسمت return View کد زیر مشخص است). بدیهی است این وهله سازی در عمل می‌تواند از طریق دسترسی به یک بانک اطلاعاتی یا یک وب سرویس و غیره باشد.

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Country = "Iran";

var employee = new Employee
{
Email = "name@site.com",
FirstName = "Vahid",
LastName = "N."
};

return View(employee);
}
}
}

امضاهای متفاوت (overloads) متد کمکی View هم به شرح زیر هستند:

ViewResult View(Object model)
ViewResult View(string viewName, Object model)
ViewResult View(string viewName, string masterName, Object model)


اکنون برای دسترسی به اطلاعات این شیء employee در View متناظر با این متد، چندین روش وجود دارد:

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country <‪br />
FirstName: @Model.FirstName
</div>

می‌توان از طریق شیء استاندارد دیگری به نام Model (که این هم یک شیء dynamic است مانند ViewBag قسمت قبل)، به خواص شیء یا مدل ارسالی به View جاری دسترسی پیدا کرد که یک نمونه از آن‌را در اینجا ملاحظه می‌کنید.
روش دوم، بر اساس تعریف صریح نوع مدل است به نحو زیر:

@model MvcApplication1.Models.Employee
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
Country: @ViewBag.Country
<‪br />
FirstName: @Model.FirstName
</div>

در اینجا در مقایسه با قبل، تنها یک سطر به اول فایل View اضافه شده است که در آن نوع شیء Model تعیین می‌گردد (کلمه model هم در اینجا با حروف کوچک شروع شده است). به این ترتیب اینبار اگر سعی کنیم به خواص این شیء دسترسی پیدا کنیم، Intellisense ویژوال استودیو ظاهر می‌شود. به این معنا که شیء Model بکارگرفته شده اینبار دیگر dynamic نیست و دقیقا می‌داند که چه خواصی را باید پیش از اجرای برنامه در اختیار استفاده کننده قرار دهد.
به این روش، روش Strongly typed view هم گفته می‌شود؛ چون View دقیقا می‌داند که چون نوعی را باید انتظار داشته باشد؛ تحت نظر کامپایلر قرار گرفته و همچنین Intellisense نیز برای آن مهیا خواهد بود.
به همین جهت این روش Strongly typed view، در بین تمام روش‌های مهیا، به عنوان روش توصیه شده و مرجح مطرح است.
به علاوه استفاده از Strongly typed views یک مزیت دیگر را هم به همراه دارد: فعال شدن یک code generator توکار در VS.NET به نام scaffolding. یک مثال ساده:
تا اینجا ما اطلاعات یک کارمند را نمایش دادیم. اگر بخواهیم یک لیست از کارمندها را نمایش دهیم چه باید کرد؟
روش کار با قبل تفاوتی نمی‌کند. اینبار در return View ما، یک شیء لیستی ارائه خواهد شد. در سمت View هم با یک حلقه foreach کار نمایش این اطلاعات صورت خواهد گرفت. راه ساده‌تری هم هست. اجازه دهیم تا خود VS.NET، کدهای مرتبط را برای ما تولید کند.
یک کلاس دیگر به پوشه مدل‌های برنامه اضافه کنید به نام Employees با محتوای زیر:

using System.Collections.Generic;

namespace MvcApplication1.Models
{
public class Employees
{
public IList<Employee> CreateEmployees()
{
return new[]
{
new Employee { Email = "name1@site.com", FirstName = "name1", LastName = "LastName1" },
new Employee { Email = "name2@site.com", FirstName = "name2", LastName = "LastName2" },
new Employee { Email = "name3@site.com", FirstName = "name3", LastName = "LastName3" }
};
}
}
}

سپس متد جدید زیر را به کنترلر Home اضافه کنید.

public ActionResult List()
{
var employeesList = new Employees().CreateEmployees();
return View(employeesList);
}

برای اضافه کردن View متناظر با آن، روی نام متد کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه ظاهر شده:


تیک مربوط به Create a strongly typed view را قرار دهید. سپس در قسمت Model class، کلاس Employee را انتخاب کنید (نه Employees جدید را، چون از آن می‌خواهیم به عنوان منبع داده لیست تولیدی استفاده کنیم). اگر این کلاس را مشاهده نمی‌کنید، به این معنا است که هنوز برنامه را یکبار کامپایل نکرده‌اید تا VS.NET بتواند با اعمال Reflection بر روی اسمبلی برنامه آن‌را پیدا کند. سپس در قسمت Scaffold template گزینه List را انتخاب کنید تا Code generator توکار VS.NET فعال شود. اکنون بر روی دکمه Add کلیک نمائید تا View نهایی تولید شود. برای مشاهده نتیجه نهایی مسیر http://localhost/Home/List باید بررسی گردد.


ج) استفاده از ViewDataDictionary

ViewDataDictionary از نوع IDictionary با کلیدی رشته‌ای و مقداری از نوع object است. توسط آن شیء‌ایی به نام ViewData در ASP.NET MVC به نحو زیر تعریف شده است:

public ViewDataDictionary ViewData { get; set; }

این روش در نگارش‌های اولیه ASP.NET MVC بیشتر مرسوم بود. برای مثال:

using System;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["DateTime"] = "<‪br/>" + DateTime.Now;
return View();
}
}
}

و سپس جهت استفاده از این ViewData تعریف شده با کلید دلخواهی به نام DateTime در View متناظر با اکشن Index خواهیم داشت:

@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div>
DateTime: @ViewData["DateTime"]
</div>

یک نکته امنیتی:
اگر به مقدار انتساب داده شده به شیء ViewDataDictionary دقت کنید، یک تگ br هم به آن اضافه شده است. برنامه را یکبار اجرا کنید. مشاهده خواهید کرد که این تگ به همین نحو نمایش داده می‌شود و نه به صورت یک سطر جدید HTML . چرا؟ چون Razor به صورت پیش فرض اطلاعات را encode شده (فراخوانی متد Html.Encode در پشت صحنه به صورت خودکار) در صفحه نمایش می‌دهد و این مساله از لحاظ امنیتی بسیار عالی است؛ زیرا جلوی بسیاری از حملات cross site scripting یا XSS را خواهد گرفت.
احتمالا الان این سؤال پیش خواهد آمد که اگر «عالمانه» بخواهیم این رفتار نیکوی پیش فرض را غیرفعال کنیم چه باید کرد؟
برای این منظور می‌توان نوشت:
@Html.Raw(myString)

و یا:
<div>@MvcHtmlString.Create("<h1>HTML</h1>")</div>

به این ترتیب خروجی Razor دیگر encode شده نخواهد بود.


د) استفاده از TempData

TempData نیز یک dictionary دیگر برای ذخیره سازی اطلاعات است و به نحو زیر در فریم ورک تعریف شده است:

public TempDataDictionary TempData { get; set; }

TempData در پشت صحنه از سشن‌های ASP.NET جهت ذخیره سازی اطلاعات استفاده می‌کند. بنابراین اطلاعات آن در سایر کنترلرها و View ها نیز در دسترس خواهد بود. البته TempData یک سری تفاوت هم با سشن معمولی ASP.NET دارد:
- بلافاصله پس از خوانده شدن، حذف خواهد شد.
- پس از پایان درخواست از بین خواهد رفت.
هر دو مورد هم به جهت بالابردن کارآیی برنامه‌های ASP.NET MVC و مصرف کمتر حافظه سرور درنظر گرفته‌ شده‌اند.
البته کسانی که برای بار اول هست با ASP.NET مواجه می‌شوند، شاید سؤال بپرسند این مسایل چه اهمیتی دارد؟ پروتکل HTTP، ذاتا یک پروتکل «بدون حالت» است یا Stateless هم به آن گفته می‌شود. به این معنا که پس از ارائه یک صفحه وب توسط سرور، تمام اشیاء مرتبط با آن در سمت سرور تخریب خواهند شد. این مورد متفاوت‌ است با برنامه‌های معمولی دسکتاپ که طول عمر یک شیء معمولی تعریف شده در سطح فرم به صورت یک فیلد، تا زمان باز بودن آن فرم، تعیین می‌گردد و به صورت خودکار از حافظه حذف نمی‌شود. این مساله دقیقا مشکل تمام تازه واردها به دنیای وب است که چرا اشیاء ما نیست و نابود شدند. در اینجا وب سرور قرار است به هزاران درخواست رسیده پاسخ دهد. اگر قرار باشد تمام این اشیاء را در سمت سرور نگهداری کند، خیلی زود با اتمام منابع مواجه می‌گردد. اما واقعیت این است که نیاز است یک سری از اطلاعات را در حافظه نگه داشت. به همین منظور یکی از چندین روش مدیریت حالت در ASP.NET استفاده از سشن‌ها است که در اینجا به نحو بسیار مطلوبی، با سربار حداقل توسط TempData مدیریت شده است.
یک مثال کاربردی در این زمینه:
فرض کنید در متد جاری کنترلر، ابتدا بررسی می‌کنیم که آیا ورودی دریافتی معتبر است یا خیر. در غیراینصورت، کاربر را به یک View دیگر از طریق کنترلری دیگر جهت نمایش خطاها هدایت خواهیم کرد.
همین «هدایت مرورگر به یک View دیگر» یعنی پاک شدن و تخریب اطلاعات کنترلر قبلی به صورت خودکار. بنابراین نیاز است این اطلاعات را در TempData قرار دهیم تا در کنترلری دیگر قابل استفاده باشد:

using System;
using System.Web.Mvc;

namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult InsertData(string name)
{
// Check for input errors.
if (string.IsNullOrWhiteSpace(name))
{
TempData["error"] = "name is required.";
return RedirectToAction("ShowError");
}
// No errors
// ...
return View();
}

public ActionResult ShowError()
{
var error = TempData["error"] as string;
if (!string.IsNullOrWhiteSpace(error))
{
ViewBag.Error = error;
}
return View();
}
}
}

در همان HomeController دو متد جدید به نام‌های InsertData و ShowError اضافه شده‌اند. در متد InsertData ابتدا بررسی می‌شود که آیا نامی وارد شده است یا خیر. اگر خیر توسط متد RedirectToAction، کاربر به اکشن یا متد ShowError هدایت خواهد شد.
برای انتقال اطلاعات خطایی که می‌خواهیم در حین این Redirect نمایش دهیم نیز از TempData استفاده شده است.
بدیهی است برای اجرا این مثال نیاز است دو View جدید برای متدهای InsertData و ShowError ایجاد شوند (کلیک راست روی نام متد و انتخاب گزینه Add view برای اضافه کردن View مرتبط با آن اکشن).
محتوای View مرتبط با متد افزودن اطلاعات فعلا مهم نیست، ولی View نمایش خطاها در ساده‌ترین حالت مثلا می‌تواند به صورت زیر باشد:

@{
ViewBag.Title = "ShowError";
}

<h2>Error</h2>

@ViewBag.Error

برای آزمایش برنامه هم مطابق مسیریابی پیش فرض و با توجه به قرار داشتن در کنترلری به نام Home، مسیر http://localhost/Home/InsertData ابتدا باید بررسی شود. چون آرگومانی وارد نشده، بلافاصله صفحه به آدرس http://localhost/Home/ShowError به صورت خودکار هدایت خواهد شد.


نکته‌ای تکمیلی در مورد Strongly typed viewها:
عنوان شد که Strongly typed view روش مرجح بوده و بهتر است از آن استفاده شود، زیرا اطلاعات اشیاء و خواص تعریف شده در یک View تحت نظر کامپایلر قرار می‌گیرند که بسیار عالی است. یعنی اگر در View بنویسم FirstName: @Model.FirstName1 چون FirstName1 وجود خارجی ندارد، برنامه نباید کامپایل شود. یکبار این را بررسی کنید. برنامه بدون مشکل کامپایل می‌شود! اما تنها در زمان اجرا است که صفحه زرد رنگ معروف خطاهای ASP.NET ظاهر می‌شود که چنین خاصیتی وجود ندارد (این حالت پیش فرض است؛ یعنی کامپایل یک View‌ در زمان اجرا). البته این باز هم خیلی بهتر است از ViewBag، چون اگر مثلا ViewBag.Country1 را وارد کنیم، در زمان اجرا تنها چیزی نمایش داده نخواهد شد؛‌ اما با روش Strongly typed view، حتما خطای Compilation Error به همراه نمایش محل مشکل نهایی، در صفحه ظاهر خواهد شد.
سؤال: آیا می‌شود پیش از اجرای برنامه هم این بررسی را انجام داد؟
پاسخ: بله. باید فایل پروژه را اندکی ویرایش کرده و مقدار MvcBuildViews را که به صورت پیش فرض false هست، true نمود. یا خارج از ویژوال استودیو با یک ادیتور متنی ساده مثلا فایل csproj را گشوده و این تغییر را انجام دهید. یا داخل ویژوال استودیو، بر روی نام پروژه کلیک راست کرده و سپس گزینه Unload Project را انتخاب کنید. مجددا بر روی این پروژه Unload شده کلیک راست نموده و گزینه edit را انتخاب نمائید. در صفحه باز شده، MvcBuildViews را یافته و آن‌را true کنید. سپس پروژه را Reload کنید.
اکنون اگر پروژه را کامپایل کنید، پیغام خطای زیر پیش از اجرای برنامه قابل مشاهده خواهد بود:

'MvcApplication1.Models.Employee' does not contain a definition for 'FirstName1' 
and no extension method 'FirstName1' accepting a first argument of type 'MvcApplication1.Models.Employee'
could be found (are you missing a using directive or an assembly reference?)
d:\Prog\MvcApplication1\MvcApplication1\Views\Home\Index.cshtml 10 MvcApplication1

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

و یک خبر خوب!
مجوز سورس کد ASP.NET MVC از MS-PL به Apache تغییر کرده و همچنین Razor و یک سری موارد دیگر هم سورس باز شده‌اند. این تغییرات به این معنا خواهند بود که پروژه از حالت فقط خواندنی MS-PL به حالت متداول یک پروژه سورس باز که شامل دریافت تغییرات و وصله‌ها از جامعه برنامه نویس‌ها است، تغییر کرده است (^ و ^).

مطالب دوره‌ها
نکته‌ای در مورد مدیریت طول عمر اشیاء در حالت HybridHttpOrThreadLocalScoped در برنامه‌های دسکتاپ
اگر به فایل IocConfig.cs پروژه دقت کرده باشید، مدیریت طول عمر واحد کار به صورت معمولی و متداولی تعریف شده است:
 cfg.For<IUnitOfWork>().Use(() => new MyWpfFrameworkContext());
و در اینجا از حالت HybridHttpOrThreadLocalScoped که در برنامه‌های وب نیز مورد استفاده قرار می‌گیرد، استفاده نشده است. چرا؟
 ThreadLocal - A single instance will be created for each requesting thread. Caches the instances with ThreadLocalStorage.
Hybrid - Uses HttpContext storage if it exists, otherwise uses ThreadLocal storage
تعاریف رسمی مرتبط با Thread local و حالت Hypbrid را در اینجا ملاحظه می‌کنید. در حالت Thread local از یک وهله در طی طول عمر یک ترد استفاده می‌شود. حالت Hybrid مشخص می‌کند که اگر برنامه وب بود، از HttpContext برای مدیریت این طول عمر استفاده کن و اگر برنامه دسکتاپ بود از ThreadLocal storage.
خوب، مشکل کجا است؟ در یک برنامه دسکتاپ اگر ترد جدیدی را ایجاد نکنیم، کل برنامه در طول مدتی که در حال اجرا است در یک ترد اصلی اجرا می‌شود. در نتیجه استفاده از حالت HybridHttpOrThreadLocalScoped در برنامه‌های دسکتاپ، بسیار شبیه به حالت Singleton است. به این ترتیب با بسته شدن یک پنجره یا هدایت به صفحه‌ای دیگر، Context برنامه و EF رها نخواهند شد (چون تضمین شده است که یک وهله از این شیء در طول عمر ترد جاری وجود داشته باشد). نتیجه آن روبرو شدن با خطاهایی است که ردیابی و دیباگ بسیار مشکلی دارند. برای مثال چندی قبل در سایت مطرح شده بود که چرا در یک برنامه WinForms خطای زیر را دریافت می‌کنم:
 An object with the same key already exists in the ObjectStateManager
علت پس از چند سؤال جواب، به مدیریت طول عمر UOW مرتبط شد. چون از حالت HybridHttpOrThreadLocalScoped استفاده شده بوده، در صفحه‌ای، شیءایی به Context اضافه شده. پس از مراجعه به صفحه‌ای دیگر، مجددا سعی در اضافه کردن همین شیء گردیده است (با این تصور که با بسته شدن صفحه قبلی، Context هم از بین رفته است). بلافاصله خطای ذکر شده، توسط EF گزارش گردیده است؛ چون هنوز Context باز است و از بین نرفته است.

در برنامه‌های وب چطور؟
در برنامه‌های وب، به ازای هر درخواست رسیده، یک ترد جدید ایجاد می‌شود. به همین جهت استفاده از حالت HybridHttpOrThreadLocalScoped برای داشتن تنها یک Context در طول یک درخواست ضروری است.

نتیجه گیری
در برنامه‌های دسکتاپ خود اگر از ترد استفاده نمی‌کنید، از حالت HybridHttpOrThreadLocalScoped برای مدیریت طول عمر UOW استفاده نکنید.
مطالب
React 16x - قسمت 5 - کامپوننت‌ها - بخش 2 - نمایش لیست‌ها و مدیریت رویدادها و حالات
در قسمت قبل، اولین کامپوننت React خود را ایجاد کردیم و سپس جزئیات بیشتری از عبارات JSX را مانند نحوه‌ی تعریف المان‌های مختلف و تنظیم مقادیر ویژگی‌های آن‌را بررسی کردیم. در ادامه‌ی همان مثال، در این قسمت، نحوه‌ی نمایش لیست‌ها و تعریف و مدیریت رویدادها را در کامپوننت‌های React، بررسی می‌کنیم.


نحوه‌ی رندر لیستی از اشیاء در کامپوننت‌های React

فرض کنید می‌خواهیم لیستی از تگ‌ها را رندر کنیم. برای این منظور ابتدا داده‌های مرتبط را به خاصیت state کامپوننت، اضافه می‌کنیم:
class Counter extends Component {
  state = {
    count: 0,
    tags: ["tag 1", "tag 2", "tag 3"]
  };
اکنون می‌خواهیم tags را توسط المان‌های ul و ui رندر کنیم. اگر با Angular کار کرده باشید، به همراه یک دایرکتیو ngFor است که توسط آن می‌توان یک حلقه را در قالب جاری، پیاده سازی و رندر کرد. اما در React و عبارات JSX، چیزی به نام مفهوم حلقه‌ها وجود خارجی ندارد؛ چون JSX یک templating engine نیست. فقط بیان ساده‌ی المان‌هایی است که قرار است توسط کامپایلر Babel به کدهای جاوا اسکریپتی ترجمه شوند. بنابراین اکنون این سؤال وجود دارد که چگونه می‌توان لیستی از عناصر را در اینجا رندر کرد؟
در مطلب «React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2» در مورد متد Array.map بحث شد. در اینجا می‌توان توسط متد map، هر المان آرایه‌ی تگ‌ها را به یک المان React تبدیل و سپس رندر کرد:
class Counter extends Component {
  state = {
    count: 0,
    tags: ["tag 1", "tag 2", "tag 3"]
  };

  render() {
    return (
      <div>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
        <ul>
          {this.state.tags.map(tag => (
            <li>{tag}</li>
          ))}
        </ul>
      </div>
    );
  }
در این مثال، داخل المان ul، با یک {} شروع می‌کنیم تا بتوان به صورت پویا به مقدار آرایه‌ی this.state.tags دسترسی پیدا کرد. سپس متد map را بر روی این آرایه فراخوانی می‌کنیم. متد map، هر عضو آرایه‌ی tags را به callback function آن ارسال کرده و خروجی آن‌را به صورت یک عبارت JSX که در نهایت به یک المان جاوا اسکریپتی خالص ترجمه خواهد شد، تبدیل می‌کند. این فرآیند سبب رندر لیست tags می‌شود:


هرچند اکنون لیستی از تگ‌ها در مرورگر رندر شده‌اند، اما در کنسول توسعه دهندگان مرورگر، یک اخطار نیز درج شده‌است. علت اینجا است که React نیاز دارد تا بتواند هر آیتم رندر شده را به صورت منحصربفردی شناسایی کند. هدف این است که بتواند در صورت تغییر state هر المان در DOM مجازی خودش، خیلی سریع تشخیص دهد که چه چیزی تغییر کرده و فقط کدام قسمت خاص را باید در DOM اصلی، درج و به روز رسانی کند. برای رفع این مشکل، ویژگی key را به هر المان li در کدهای فوق اضافه می‌کنیم:
<li key={tag}>{tag}</li>
البته در مثال ما تگ‌ها منحصربفرد هستند؛ بنابراین استفاده‌ی از آن‌ها به عنوان key، مشکلی را ایجاد نمی‌کند. در یک برنامه‌ی مفصل‌تر، تگ‌ها می‌توانند شیء بوده و هر شیء دارای خاصیت id باشد که در این حالت فرضی می‌توان از tag.id به عنوان key استفاده کرد. همچنین باید دانست که این key فقط نیاز است در لیست ul، منحصربفرد باشد و نیازی نیست تا در کل DOM منحصربفرد باشد.


رندر شرطی عناصر در کامپوننت‌های React

در اینجا می‌خواهیم اگر تگی وجود نداشت، پیام متناسبی ارائه شود؛ در غیراینصورت لیست تگ‌ها همانند قبل نمایش داده شود (رندر شرطی یا conditional rendering). برای انجام اینکار در React، برخلاف Angular، دارای دایرکتیوهای ساختاری if/else نیستیم؛ چون همانطور که عنوان شد، JSX یک templating engine نیست. به همین جهت برای رندر شرطی المان‌ها در React، باید از همان جاوا اسکریپت خالص کمک بگیریم:
  renderTags() {
    if (this.state.tags.length === 0) {
      return <p>There are no tags!</p>;
    }

    return (
      <ul>
        {this.state.tags.map(tag => (
          <li key={tag}>{tag}</li>
        ))}
      </ul>
    );
  }
یک روش حل این مساله، نوشتن متدی است که به همراه یک if/else است. در اینجا اگر آرایه‌ی تگ‌ها، دارای عنصری نبود، یک پاراگراف متناظر نمایش داده می‌شود، در غیراینصورت همان قسمت رندر لیست تگ‌ها را که توسعه دادیم، بازگشت می‌دهیم. بنابراین این متد، دو خروجی JSX را بسته به شرایط مختلف می‌تواند داشته باشد. سپس از این متد به صورت {()this.renderTags} در متد render اصلی استفاده می‌کنیم:
  render() {
    return (
      <div>
        <span className={this.getBadgeClasses()}>{this.formatCount()}</span>
        <button className="btn btn-secondary btn-sm">Increment</button>
        {this.renderTags()}
      </div>
    );
  }
برای آزمایش آن هم یکبار آرایه‌ی tags را به نحو زیر خالی کنید:
  state = {
    count: 0,
    tags: []
  };

روش دوم حل این نوع مساله‌ها، استفاده از روش زیر است؛ در این حالت خاص، فقط یک if را داریم، بدون وجود قسمت else:
{this.state.tags.length === 0 && "Please create a new tag!"}
ابتدا شرط مدنظر نوشته می‌شود، سپس پیامی را که باید در این حالت ارائه شود، پس از && می‌نویسیم. در مثال فوق اگر آرایه‌ی tags خالی باشد، پیامی نمایش داده می‌شود.
اما این روش چگونه کار می‌کند؟! در اینجا && را به دو مقدار مشخص اعمال کرده‌ایم. یکی حاصل یک مقایسه است و دیگری یک مقدار رشته‌ای مشخص. در جاوا اسکریپت برخلاف سایر زبان‌های برنامه نویسی، می‌توان && را بین دو مقدار غیر Boolean نیز اعمال کرد. در جاوا اسکریپت، یک رشته‌ی خالی به false تعبیر می‌شود و اگر تنها دارای یک حرف باشد، true درنظر گرفته می‌شود. برای نمونه در ترکیب 'true && 'Hi، هر دو قسمت به true تفسیر می‌شوند. در این حالت موتور جاوا اسکریپت، دومین عبارت (آخرین عبارت && شده) را بازگشت می‌دهد. همچنین در جاوا اسکریپت عدد صفر به false تفسیر می‌شود. بنابراین ترکیب true && 'Hi' && 1 مقدار 1 را بازگشت می‌دهد؛ چون عدد 1 هم از دیدگاه جاوا اسکریپت به true تفسیر خواهد شد.


مدیریت رخ‌دادها در React


همانطور که در تصویر فوق نیز مشاهده می‌کنید، رخ‌دادهای استاندارد DOM، دارای خواص معادل React ای نیز هستند. برای مثال زمانیکه می‌نویسیم onClick، دقیقا متناظر است با یک خاصیت المان React در عبارات JSX. بنابراین این نام‌ها حساس به کوچکی و بزرگی حروف نیز هستند.
روش تعریف متدهای رخ‌دادگردان در اینجا، با ذکر فعل handle شروع می‌شود:
  handleIncrement() {
    console.log("Increment clicked!");
  }
سپس ارجاعی از این متد را (نه فراخوانی آن‌را)، به خاصیت برای مثال onClick ارسال می‌کنیم:
<button
    onClick={this.handleIncrement}
    className="btn btn-secondary btn-sm"
>
    Increment
</button>
اگر دقت کنید، onClick، ارجاع this.handleIncrement را دریافت کرده‌است (یعنی بدون () ذکر شده‌است) و نه فراخوانی این متد را (با ذکر ()).
اکنون اگر این فایل را ذخیره کرده و خروجی را در مرورگر بررسی کنیم، با هربار کلیک بر روی دکمه‌ی Increment، یک console.log صورت می‌گیرد.

در ادامه می‌خواهیم در این رخ‌دادگردان، مقدار this.state.count را افزایش دهیم. برای این منظور ابتدا مقدار this.state.count را به نحو زیر لاگ می‌کنیم:
  handleIncrement() {
    console.log("Increment clicked!", this.state.count);
  }
پس از ذخیره‌ی فایل و اجرای برنامه، اینبار با کلیک بر روی دکمه‌ی Increment، بلافاصله خطای «Uncaught TypeError: Cannot read property 'state' of undefined» در کنسول توسعه دهنده‌های مرورگر ظاهر می‌شود. عنوان می‌کند که شیء this در این متد، undefined است؛ بنابراین امکان خواندن خاصیت state از آن وجود ندارد.


bind مجدد شیء this در رخ‌دادگردان‌های React

در مورد this و bind مجدد آن در مطلب «React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1» مفصل بحث کردیم و در اینجا می‌خواهیم از نتایج آن استفاده کنیم.
همانطور که مشاهده کردید، در متد رویدادگران handleIncrement، به شیء this دسترسی نداریم. چرا؟ چون this در جاوا اسکریپت نسبت به سایر زبان‌های برنامه نویسی، متفاوت رفتار می‌کند. بسته به اینکه یک متد یا تابع، چگونه فراخوانی می‌شود، this می‌تواند اشیاء متفاوتی را بازگشت دهد. اگر تابعی به عنوان یک متد و جزئی از یک شیء فراخوانی شود، this در این حالت همواره ارجاعی را به آن شیء باز می‌گرداند. اما اگر آن تابع به صورت متکی به خود فراخوانی شد، به صورت پیش‌فرض ارجاعی را به شیء سراسری window مرورگر، بازگشت می‌دهد و اگر strict mode فعال باشد، تنها undefined را بازگشت می‌دهد. به همین جهت است که در اینجا خطای undefined بودن this را دریافت می‌کنیم.
یک روش حل این مشکل که پیشتر نیز در مورد آن توضیح دادیم، استفاده از متد bind است:
  constructor() {
    super();
    console.log("constructor", this);
    this.handleIncrement = this.handleIncrement.bind(this);
  }
زمانیکه شیءای از نوع کلاس جاری ایجاد می‌شود، متد constructor آن نیز فراخوانی خواهد شد. در این مرحله دسترسی کاملی به شیء this وجود دارد که نمونه‌ی آن‌را با console.log نوشته شده می‌توانید آزمایش کنید. در اینجا چون کامپوننت جاری از کلاس Component مشتق شده‌است، پیش از دسترسی به شیء this، نیاز است سازنده‌ی کلاس پایه توسط متد super فراخوانی شود. اکنون که به this دسترسی داریم، می‌توان توسط متد bind، مقدار شیء this شیءای دیگر مانند this.handleIncrement را تنظیم مجدد کنیم (متدها نیز در جاوا اسکریپت شیء هستند). خروجی آن، یک وهله‌ی جدید از شیء handleIncrement است که this آن اینبار به وهله‌ای از شیء جاری اشاره می‌کند. به همین جهت خروجی آن‌را به this.handleIncrement انتساب می‌دهیم تا مشکل تعریف نشده بودن this آن برطرف شود.
اکنون اگر برنامه را اجرا کنید، با کلیک بر روی دکمه‌ی Increment، بجای this.state.count لاگ شده، مقدار آن که صفر است، در کنسول توسعه دهنده‌های مرورگر ظاهر می‌شود.


این یک روش است که کار می‌کند؛ اما کمی طولانی است و به ازای هر روال رویدادگردانی باید دقیقا به همین نحو تکرار شود. روش دیگر، تبدیل متد handleIncrement به یک arrow function است و همانطور که در قسمت دوم این سری نیز بررسی کردیم، arrow functionها، this شیء جاری را بازنویسی نمی‌کنند؛ بلکه آن‌را به ارث می‌برند. بنابراین ابتدا کدهای سازنده‌ی فوق را حذف می‌کنیم (چون دیگر نیازی به آن‌ها نیست) و سپس متد handleIncrement سابق را به صورت زیر، تبدیل به یک arrow function می‌کنیم:
  handleIncrement = () => {
    console.log("Increment clicked!", this.state.count);
  }
به این ترتیب با کلیک بر روی دکمه‌ی Increment، مجددا همان خروجی تصویر قبلی را دریافت می‌کنیم؛ این روش ساده‌تر و تمیزتر است و نیازی به rebind دستی تک تک رویدادگردان‌های کامپوننت جاری در این حالت وجود ندارد.


به روز رسانی state در کامپوننت‌های React

اکنون که در روال رویدادگردان handleIncrement به شیء this و سپس مقدار this.state.count آن دسترسی پیدا کرده‌ایم، می‌خواهیم با هربار کلیک بر روی این دکمه، یک واحد مقدار آن‌را افزایش داده و در UI نمایش دهیم.
در React، خواص شیء state را جهت نمایش آن‌ها در UI، مستقیما تغییر نمی‌دهیم. به عبارت دیگر نوشتن یک چنین کدی در React برای به روز رسانی UI، مرسوم نیست:
  handleIncrement = () => {
    this.state.count++;
  };
اگر تغییر فوق را اعمال و سپس برنامه را اجرا کنید، با کلیک بر روی دکمه‌ی Increment ... اتفاقی رخ نمی‌دهد! رفتار React با Angular متفاوت است و در اینجا هرچند توسط فراخوانی {()this.formatCount} کار نمایش خاصیت count انجام می‌شود، اما به ظاهر، تغییرات مقدار count، به عبارات JSX متصل نیست. در کامپوننت‌های Angular اگر مقدار خاصیتی را تغییر دهید و اگر این خاصیت در قالب آن کامپوننت، به آن خاصیت bind شده باشد، شاهد به روز رسانی آنی UI خواهید بود (Change Detection آنی و به ازای هر تغییری)؛ اما در React خیر. هرچند در همان Angular هم توصیه می‌شود که از حالت changeDetection: ChangeDetectionStrategy.OnPush برای رسیدن به حداکثر کارآیی نمایشی کامپوننت‌ها استفاده شود؛ حالت OnPush در Angular، به روش تشخیص تغییرات React که در ادامه توضیح داده می‌شود، بیشتر شبیه است.

در کدهای فوق هرچند با کلیک بر روی دکمه‌ی Increment، مقدار count افزایش یافته‌است، اما React از وقوع این تغییرات مطلع نیست. به همین جهت است که هیچ تغییری را در UI برنامه مشاهده نمی‌کنید.
با اجرای قطعه کد فوق، یک چنین اخطاری نیز در کنسول توسعه دهندگان مرورگر ظاهر می‌شود:
  Line 33:5:  Do not mutate state directly. Use setState()  react/no-direct-mutation-state

برای رفع این مشکل باید از یکی از متدهای به ارث برده شده‌ی از کلاس پایه‌ی Component، به نام setState استفاده کرد. به این ترتیب به React اعلام می‌کنیم که state تغییر کرده‌است (فعالسازی Change Detection، فقط در صورت نیاز). سپس React شروع به محاسبه‌ی تغییرات کرده و در نتیجه قسمت‌های متناظری از UI را برای هماهنگ سازی DOM مجازی خودش با DOM اصلی، به روز رسانی می‌کند.
زمانیکه از متد setState استفاده می‌کنیم، شیءای را باید به صورت یک پارامتر به آن ارسال کنیم. در این حالت مقادیر آن یا به خاصیت state جاری اضافه می‌شوند و یا در صورت از پیش موجود بودن، همان خواص را بازنویسی می‌کنند:
  handleIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };
در اینجا به متد this.setState که از قسمت extends Component جاری به ارث رسیده‌است، یک شیء را با خاصیت count و مقدار جدیدی، ارسال می‌کنیم.
در این مرحله، فایل جاری را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر، بر روی دکمه‌ی Increment کلیک کنید. اینبار ... کار می‌کند! چون React از تغییرات مطلع شده‌است:


وقتی state تغییر می‌کند، چه اتفاقاتی رخ می‌دهند؟

با فراخوانی متد this.setState، به React اعلام می‌کنیم که state یک کامپوننت قرار است تغییر کند. سپس React فراخوانی مجدد متد Render را در صف اجرایی خودش قرار می‌دهد تا در زمانی در آینده، اجرا شود؛ این فراخوانی async است. کار متد render، بازگشت یک المان جدید React است. در اینجا DOM مجازی React از چند المان، به صورت یک div و دو فرزند دکمه و span تشکیل شده‌است. در این حالت یک DOM مجازی قدیمی نیز از قبل (پیش از اجرای مجدد متد render) وجود دارد. در این لحظه، React این دو DOM مجازی را کنار هم قرار می‌دهد و محاسبه می‌کند که در اینجا دقیقا کدام المان‌ها نسبت به قبل تغییر کرده‌اند. برای نمونه در اینجا تشخیص می‌دهد که span است که تغییر کرده، چون مقدار count، توسط آن نمایش داده می‌شود. در این حالت از کل DOM اصلی، تنها همان span تغییر کرده را به روز رسانی می‌کند و نه کل DOM را (و نه اعمال مجدد کل المان‌های حاصل از متد render را).
این مورد را می‌توان به نحو زیر آزمایش و مشاهده کرد:
در مرورگر بر روی المان span که شماره‌ها را نمایش می‌دهد، کلیک راست کرده و گزینه‌ی inspect را انتخاب کنید. سپس بر روی دکمه‌ی Increment کلیک نمائید. مرورگر قسمتی را که به روز می‌شود، با رنگی مشخص و متمایز، به صورت لحظه‌ای نمایش می‌دهد:



ارسال پارامترها به متدهای رویدادگردان

تا اینجا متد handleIncrement، بدون پارامتر تعریف شده‌است. فرض کنید در یک برنامه‌ی واقعی قرار است با کلیک بر روی این دکمه، id یک محصول را نیز به handleIncrement، منتقل و ارسال کنیم. اما در onClick={this.handleIncrement} تعریف شده، یک ارجاع را به متد handleIncrement داریم. بنابراین برای حل این مساله نمی‌توان از روشی مانند onClick={this.handleIncrement(1)} استفاده کرد که در آن عدد فرضی 1 به صورت آرگومان متد handleIncrement ذکر شده‌است.
یک روش حل این مساله، تعریف متد دومی است که متد handleIncrement پارامتر دار را فراخوانی می‌کند:
  doHandleIncrement = () => {
    this.handleIncrement({ id: 1, name: "Product 1" });
  };
و در این حالت برای مثال متد handleIncrement یک شیء را پذیرفته‌است:
  handleIncrement = product => {
    console.log(product);
    this.setState({ count: this.state.count + 1 });
  };
سپس بجای تعریف onClick={this.handleIncrement}، از متد doHandleIncrement استفاده خواهیم کرد؛ یعنی onClick={this.doHandleIncrement}

هرچند این روش کار می‌کند، اما بیش از اندازه طولانی شده‌است. راه حل بهتر، استفاده از یک inline function است:
onClick={() => this.handleIncrement({ id: 1, name: "Product 1" })}
یعنی کل arrow function مربوط به doHandleIncrement را داخل onClick قرار می‌دهیم و چون یک سطری است، نیازی به ذکر {} و سمی‌کالن انتهای آن‌را هم ندارد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-04-part02.zip
اشتراک‌ها
درک بیشتر WCF به کمک مثال(ویدیوی فارسی)
در این ویدیو یک مثال بررسی شده است. داشتن اطلاعات ابتدایی از WCF برای درک این ویدیو کافیست.
سولوشنی حاوی دو پروژه تشکیل داده شده است. یکی از پروژه ها، یک سرویس و دومین پروژه یک وب فرم است که قرار است با آن سرویس ارتباط برقرار کند. همچنین در قلب پروژه‌ی وب فرم یک سرویس دیگر قرار داده ایم و دو موضوع را بررسی کرده ایم:
چگونه یک وب فرم با سرویس موجود درون پروژه‌ی خودش تعامل کند؟
چگونه یک وب فرم با یک سرویس خارج از پروژه‌ی خودش تعامل کند؟
- سرویس‌ها از نوع Restful تعریف شده اند.
- داده‌ها از طریق Json ارسال و دریافت شده اند.
- روش POST و GET در هر دو پروژه بررسی شده است. 
- نحوه ارسال و دریافت اطلاعات در فرمت Json به کمک سی شارپ بررسی شده است
درک بیشتر WCF به کمک مثال(ویدیوی فارسی)
نظرات مطالب
باگ Directory Traversal در سایت
بله. همینطوره. به شرطی‌که امکانات سخت افزاری مهیا چنین اجازه‌ای رو به شما بدهد. مثلا سرور اختصاصی داشته باشید با RAM و CPU قابل قبول. به عبارتی در شبکه‌های خصوصی شرکت‌ها، نه سایت‌های عمومی با حداقل‌هایی که در اختیار دارند. یا پردازش ابری ... مثلا برای سازمان‌هایی که می‌تونند هزینه کنند یا دسترسی به این امکانات دارند.