مطالب
امکان استفاده از یک هارد SSD بجای RAM در SQL Server 2014
Buffer Pool یکی از مصرف کنندگان اصلی حافظه در SQL Server است. برای مثال زمانیکه اطلاعاتی را از بانک اطلاعاتی دریافت می‌کنید، این داده‌ها در Buffer Pool کش می‌شوند. همچنین SQL Server اطلاعات کلیه Execution Plans را نیز در Plan Cache که جزئی از Buffer Pool است، برای استفاده‌ی مجدد نگهداری می‌کند. هر چقدر حافظه‌ی فیزیکی سرور شما بیشتر باشد، مقدار Buffer Pool نیز به همین میزان افزایش خواهد یافت که البته حداکثر آن‌را می‌توان در تنظیمات حافظه‌ی سرور محدود کرد (Max Server Memory setting).
در دنیای واقعی میزان حافظه‌ی فیزیکی سرورها محدود است. در SQL Server 2014 راه حلی برای این مشکل تحت عنوان Buffer Pool Extensions ارائه شده‌است که محل قرارگیری آن‌را در تصویر ذیل مشاهده می‌کنید:


Buffer Pool Extensions از یک فایل ساده که به آن Extension File نیز گفته می‌شود، تشکیل شده‌است و امکان ذخیره سازی آن بر روی هاردهای سریعی مانند SSD Driveها میسر است. این فایل، ساختاری را همانند page file، در سیستم عامل ویندوز دارد. در این حالت بجای اضافه کردن RAM بیشتر به سرور، یک Extension File را می‌توان بکار گرفت. هر زمان که Buffer Pool اصلی تحت فشار قرار گیرد (به میزان حافظه‌ای بیش از حافظه‌ی فیزیکی سرور نیاز باشد)، از این افزونه‌ی فایلی استفاده خواهد شد.
اطلاعات جزئیات Buffer Pool را توسط کوئری ذیل می‌توان بدست آورد:
 Select * from sys.dm_os_buffer_descriptors


نحوه‌ی فعال سازی و تنظیم Buffer Pool Extensions

قبل از هر کاری بهتر است وضعیت افزونه‌ی Buffer pool را بررسی کرد:
 select * from sys.dm_os_buffer_pool_extension_configuration


همانطور که ملاحظه می‌کنید، در حالت پیش فرض غیرفعال است.
سپس یک فایل یک گیگابایتی را به عنوان افزونه‌ی Buffer pool ایجاد می‌کنیم.
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION ON
 (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 1GB);
توصیه شده‌است که این فایل را در یک درایور پر سرعت SSD قرار دهید؛ ولی محدودیتی از لحاظ محل قرارگیری ندارد (هر چند به نظر فقط در حالتیکه از SSD Drive استفاده شود واقعا کار می‌کند).
اینبار اگر کوئری اول را اجرا کنیم، چنین خروجی قابل مشاهده است:


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


تغییر اندازه‌ی افزونه‌ی Buffer pool

اگر سعی کنیم، یک گیگابایت را مثلا به 10 گیگابایت افزایش دهیم:
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION ON
 (FILENAME = 'd:\BufferPoolExt.BPE', SIZE = 10GB);
با خطای ذیل مواجه خواهیم شد:
 Could not change the value of the 'BPoolExtensionPath' property
برای رفع این مشکل، ابتدا باید افزونه‌ی Buffer pool را غیرفعال کرد:
 ALTER SERVER CONFIGURATION
SET BUFFER POOL EXTENSION OFF
سپس می‌توان مجددا اندازه و یا مسیر دیگری را مشخص کرد. بهتر است اندازه‌ی این فایل را حدود 16 برابر حداکثر میزان حافظه‌ی سرور (Max Server Memory) تعیین کنید.
همچنین توصیه شده‌است که پس از غیرفعال کردن این افزونه، بهتر است یکبار instance جاری را ری استارت کنید.


چه زمانی بهتر است از افزونه‌ی Buffer pool استفاده شود؟
در محیط‌های read-heavy OLTP، استفاده از یک چنین افزونه‌ای می‌تواند میزان کارآیی و پاسخگویی سیستم را به شدت افزایش دهد (تا 50 درصد).


سؤال: آیا غیرفعال کردن افزونه‌ی Buffer pool سبب از دست رفتن اطلاعات می‌شود؟
خیر. BPE، تنها clean pages را در خود ذخیره می‌کند؛ یعنی تنها اطلاعاتی که Commit شده‌اند در آن حضور خواهند داشت و در این حالت حذف آن یا ری استارت کردن سرور، سبب از دست رفتن اطلاعات نخواهند شد.


برای مطالعه بیشتر

Buffer Pool Extension
SQL Server 2014 Buffer Pool Extensions
Do you require a SSD to use the Buffer Pool Extension feature in SQL Server 2014
Buffer Pool Extensions in SQL Server 2014
SQL Server 2014 – Buffer Pool Extension
مطالب
اعمال SEO بر روی AngularJS
در این بخش قصد داریم سئو را بر روی یک برنامه‌ی نوشته شده با آنگلولار و Asp.net Mvc اعمال نماییم. انگولار جی‌اس، صفحات را با  استفاده از جاوااسکریپت رندر میکند، ولی اکثر کرالر‌ها نمیتوانند جاوااسکریپت را اجرا کنند و موقع اجرای صفحات سایت ما  فقط یک div خالی را میبینند.
کاری که سرویس Prerender یا فیلتر سفارشی AjaxCrawlable برای ما انجام میدهد، درخواست‌هایی را که از طرف کرالرها آمده‌است را شناسایی میکند و مانند یک مرورگر، با استفاده از phantomjs آنرا اجرا میکند و نتیجه‌ی کامل صفحات ما را به صورت اچ تی ام ال استاتیک برمی‌گرداند.
فانتوم جی اس، موتور اختصاصی برای شبیه سازی مرورگر مبتنی بر Webkit می‌باشد. فانتوم جی اس را میتوانید بر روی ویندوز، لینوکس و مک نصب نمایید. فانتوم جی اس یک Console در اختیار برنامه نویس قرار می‌دهد که می‌توان توسط آن، برنامه‌های جاوااسکریپت را اجرا نمود. همچنین فانتوم جی اس میتواند اسکرین شاتی را نیز از محتوای وب سایت ما فراهم نماید.
 برای اینکه صفحات انگولار جی اس،ایندکس شوند سه مرحله وجود دارند:
1- به کرالر اطلاع دهیم که رندر کردن سایت، توسط جاوااسکریپت انجام میگردد؛ با اضافه کردن متاتگ زیر در اچ تی ام ال سایت (البته در حالت استفاده HTML5 push state ) :
<meta name="fragment" content="!">
<base href="/">
2- بعد از اضافه کردن متاتگ بالا، کرالر درخواست‌های خود را به صورت زیر به سایت ما ارسال میکند:
http://www.example.com/?_escaped_fragment_=
ما در این مثال از  HTML5 push state  استفاده میکنیم. بنابراین لینکی مانند http://www.example.com/user/123 توسط کرالر به صورت زیر دیده میشود: 
http://www.example.com/user/123?_escaped_fragment_=
3- اچ تی ام ال کاملا رندر شده توسط سایت ما به کرالر ارسال گردد.
برای رندر کردن  اچ تی ام ال صفحات، چندین روش وجود دارد:
روش اول: میتوانیم از سرویس‌های آماده‌ای همچون Prerender.io   استفاده کنیم که سرویسهایی را برای زبانهای مختلف ارائه کرده‌اند. باتوجه به توضیحات نمونه استفاده از آن در Asp.Net Mvc کافیست در سایت Prerender.io  ثبت نام کرده، Token را دریافت کنیم و در کانفیگ برنامه قرار دهیم و در کلاس PreStart قطعه کد زیر را قرار دهیم:
DynamicModuleUtility.RegisterModule(typeof(Prerender.io.PrerenderModule));
مثال استفاده از Prerender.io را میتوانید از این آدرس Simple_Demo_Prerender.zip دانلود نمایید.
 
یکی از ابزارهای مناسب تست کردن اینکه صفحات توسط کرالر ایندکس میشوند یا خیر، برنامه screamingfrog میباشد.
در پنل Ajax آن، صفحات ایندکس شده ما نمایش داده میشوند. لینکی مشابه زیر را در مرورگر اجرا کرده، با ViewPage Source کردن آن میتوانید نتیجه اچ تی ام ال کاملا رندر شده را مشاهده نمایید.
http://www.example.com/user/123?_escaped_fragment_=
نسخه رایگان سرویس Prerender.io تا 250 صفحه را پوشش میدهد.

روش دوم: فیلتر سفارشی AjaxCrawlable. در اولین قدم نیاز به نصب فانتوم جی اس داریم:
<package id="PhantomJS" version="1.9.2" targetFramework="net452" />
<package id="phantomjs.exe" version="1.9.2.1" targetFramework="net452" />
فایل phantomjs.exe را از پوشه packages\PhantomJS.1.9.2\tools\phantomjs\phantomjs.exe یافته و در پوشه bin برنامه قرار دهید. با Attribute زیر هر درخواستی که توسط کرالر ارسال گردد به اکشن returnHTML منتقل میگردد.
برای اینکه خطای معروف A potentially dangerous Request.Form value was detected from the client را دریافت نکنیم، کافیست قسمتهایی از آدرس را که شامل کاراکترهای خاصی مانند :// میباشند، از url حذف کنیم و در اکشن returnHtml قسمتهای حذف شده را  به url  اضافه نماییم.
کرالرها  با مشاهده تگ fragment، تمام لینکها را به همراه کوئری استرینگ _escaped_fragment_  میفرستند، که ما در سرور باید آنرا  با رشته خالی جایگزین نماییم.
 public class AjaxCrawlableAttribute : System.Web.Mvc.ActionFilterAttribute
    {
        private const string Fragment = "_escaped_fragment_";
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var request = filterContext.RequestContext.HttpContext.Request;
            var url = request.Url.ToString();
            if (request.QueryString[Fragment] != null && !url.Contains("HtmlSnapshot/returnHTML"))
            {
                url = url.Replace("?_escaped_fragment_=", string.Empty).Replace(request.Url.Scheme + "://", string.Empty);
                url = url.Split(':')[1];
                filterContext.Result = new RedirectToRouteResult(
                   new RouteValueDictionary { { "controller", "HtmlSnapshot" }, { "action", "returnHTML" }, { "url", url } });
            }
            return;
        }
    }
Route‌های پیشفرض را با کدهای زیر جایگزین میکنیم:
public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
             name: "HtmlSnapshot",
             url: "HtmlSnapshot/returnHTML/{*url}",
             defaults: new { controller = "HtmlSnapshot", action = "returnHTML", url = UrlParameter.Optional });

            routes.MapRoute(
            name: "SPA",
            url: "{*catchall}",
            defaults: new { controller = "Home", action = "Index" })
        }
 اضافه کردن این فیلتر به فیلترهای Asp.net Mvc 
 public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new AjaxCrawlableAttribute());
        }
    }
ایجاد کنترلر HtmlSnapshot و متد returnHTML :
Url را به عنوان آرگومان به تابع page.open فایل جاوااسکریپتی فانتوم میدهیم و بعد از اجرای کامل، خروجی را درViewData قرار میدهیم 
public ActionResult returnHTML(string url)
        {
            var prefix = HttpContext.Request.Url.Scheme + "://" + HttpContext.Request.Url.Host+":";
            url = prefix+url;
            string appRoot = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
            var startInfo = new ProcessStartInfo
            {
                Arguments = string.Format("{0} {1}", Path.Combine(appRoot, "Scripts\\seo.js"), url),
                FileName = Path.Combine(appRoot, "bin\\phantomjs.exe"),
                UseShellExecute = false,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                RedirectStandardInput = true,
                StandardOutputEncoding = System.Text.Encoding.UTF8
            };
            var p = new Process();
            p.StartInfo = startInfo;
            p.Start();
            string output1 = p.StandardOutput.ReadToEnd();
            p.WaitForExit();
            ViewData["result"] = output1.Replace("<!-- ngView:  -->", "").Replace("ng-view=\"\"", "");
            return View();
        }
در فایل renderHtml.cshtml
@{ 
    Layout = null;
}
@Html.Raw(ViewBag.result)
ایجاد فایل seo.js  در پوشه Scripts سایت :
در این بخش webpage  را ایجاد میکنیم و آدرس صفحه را از[system.args[1  دریافت کرده و عملیات کپچر کردن را آغاز میکنیم و بعد از تکمیل اطلاعات در سرور، کد زیر اجرا میشود:
console.log(page.content)

var page = require('webpage').create();
var system = require('system');

var lastReceived = new Date().getTime();
var requestCount = 0;
var responseCount = 0;
var requestIds = [];
var startTime = new Date().getTime();;
page.onResourceReceived = function (response) {
    if (requestIds.indexOf(response.id) !== -1) {
        lastReceived = new Date().getTime();
        responseCount++;
        requestIds[requestIds.indexOf(response.id)] = null;
    }
};
page.onResourceRequested = function (request) {
    if (requestIds.indexOf(request.id) === -1) {
        requestIds.push(request.id);
        requestCount++;
    }
};

function checkLoaded() {
    return page.evaluate(function () {
        return document.all["compositionComplete"];
    }) != null;
}
// Open the page
page.open(system.args[1], function () {

});

var checkComplete = function () {
    // We don't allow it to take longer than 5 seconds but
    // don't return until all requests are finished
    if ((new Date().getTime() - lastReceived > 300 && requestCount === responseCount) || new Date().getTime() - startTime > 10000 || checkLoaded()) {
        clearInterval(checkCompleteInterval);
        console.log(page.content);
        phantom.exit();
    }
}
// Let us check to see if the page is finished rendering
var checkCompleteInterval = setInterval(checkComplete, 300);
صفحه Layout.Cshtml
<!DOCTYPE html>
<html ng-app="appOne">
<head>
    <meta name="fragment" content="!">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta charset="utf-8" />
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />
    <base href="/">
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
    <script src="~/Scripts/angular/angular.js"></script>
    <script src="~/Scripts/angular/angular-route.js"></script>
    <script src="~/Scripts/angular/angular-animate.js"></script>
    <script>
        angular.module('appOne', ['ngRoute'], function ($routeProvider, $locationProvider) {
            $routeProvider.when('/one', {
                template: "<div>one</div>", controller: function ($scope) {
                }
            })
            .when('/two', {
                template: "<div>two</div>", controller: function ($scope) {
                }
            }).when('/', {
                template: "<div>home</div>", controller: function ($scope) {
                }
            });
            $locationProvider.html5Mode({
                enabled: true
            });
        });
    </script>
</head>
<body>
    <div id="body">
        <section ng-view></section>
        @RenderBody()
    </div>
    <div id="footer">
        <ul class='xoxo blogroll'>
            <li><a href="one">one</a></li>
            <li><a href="two">two</a></li>
        </ul>
    </div>
</body>
</html>

چند نکته تکمیلی:
* فانتوم جی اس قادر به اجرای لینکهای فارسی (utf-8) نمیباشد.
 * اگر خطای syntax error را دریافت کردید ممکن است پروژه شما در مسیری طولانی در روی هارد دیسک قرار داشته باشد.
مطالب
مدیریت پیشرفته‌ی حالت در React با Redux و Mobx - قسمت هفتم - بررسی مفاهیم Mobx
MobX از 4 مفهوم اصلی تشکیل می‌شود:

- Observable state: در MobX نیز همانند Redux، کل شیء state به صورت یک شیء جاوا اسکریپتی ارائه می‌شود؛ با این تفاوت که در اینجا این شیء، یک Observable است که نمونه‌ای از مفهوم آن‌را در مثال قسمت قبل بررسی کردیم.
- Actions: متدهایی هستند که state را تغییر می‌دهند.
- Computed properties: در مورد مفهوم خواص محاسباتی در قسمت قبل بحث کردیم. این خواص، مقدار خود را بر اساس تغییرات سایر خواص Observable، به روز می‌کنند.
- Reactions: سبب بروز اثرات جانبی (side effects) می‌شوند؛ مانند تعامل با دنیای خارج. نمونه‌ای از آن، متد autorun است که تغییرات Observableها را ردیابی می‌کند.

برای مثال خاصیت محاسباتی fullName، تغییرات سایر خواص Observable را احساس کرده و مقدار خودش را به روز می‌کند. سپس یک Reaction به آن، می‌تواند به روز رسانی DOM، جهت نمایش این تغییرات باشد و یا نمونه‌ی دیگری که می‌تواند بسیاری از این مفاهیم را نمایش دهد، کلاس زیر است:
import { action, observable, computed } from 'mobx';

class PizzaCalculator {
  @observable numberOfPeople = 0;
  @observable slicesPerPerson = 2;
  @observable slicesPerPie = 8;

  @computed get slicesNeeded() {
    console.log('Getting slices needed');
    return this.numberOfPeople * this.slicesPerPerson;
   }

  @computed get piesNeeded() {
    console.log('Getting pies needed');
    return Math.ceil(this.slicesNeeded / this.slicesPerPie);
   }

   @action addGuest() {
     this.numberOfPeople!++;
   }
}
- دراینجا با استفاده از decorator syntax کتابخانه‌ی mobx، خواص و متدهای این کلاس معمولی ES6 را مزین کرده‌ایم.
- برای مثال زمانیکه تعریف observable numberOfPeople@ را داریم، به این معنا است که می‌خواهیم تغییرات تعداد افراد را تحت نظر قرار دهیم و اگر تغییری در مقدار آن صورت گرفت، آنگاه مقدار خواص محاسباتی که با computed@ مزین شده‌اند، به صورت خودکار به روز رسانی شوند.
- action@ به این معنا است که متدی در اینجا، سبب بروز تغییری در state کلاس جاری می‌شود. MobX به همراه یک strict mode است که اگر فعال باشد، ذکر تزئین کننده‌ی action@ بر روی یک چنین متدهایی ضروری است، در غیراینصورت، الزامی به درج آن نیست.

در این قطعه کد تعدای console.log را هم ملاحظه می‌کنید. علت آن نمایش مفهوم کش کردن اطلاعات در MobX است. فرض کنید برای بار اول، مقدار یکی از خواصی را که به صورت observable تعریف شده‌اند، تغییر می‌دهیم. در این حالت تمام خواص محاسباتی وابسته‌ی به آن‌ها، به صورت خودکار مجددا محاسبه شده و console.log‌ها را نیز مشاهده خواهیم کرد. اگر برای بار دوم همین فراخوانی صورت گیرد و تغییری در مقادیر خواص observable صورت نگیرد، MobX از نگارش کش شده‌ی این خواص محاسباتی استفاده می‌کند و بی‌جهت سبب رندر مجدد UI نخواهد شد که در نهایت کارآیی بالایی را سبب خواهد شد. برای پیاده سازی یک چنین قابلیتی با Redux باید از مفهومی مانند React.memo و Memoization و کتابخانه‌های کمکی مانند Reselect استفاده کرد؛ اما در اینجا به صورت توکار و خودکار اعمال می‌شود.


ساختارهای داده‌ای که توسط MobX پشتیبانی می‌شوند

MobX از اکثر ساختارهای داده‌ای متداول در جاوا اسکریپت پشتیبانی می‌کند؛ برای مثال:
- اشیاء مانند ({})observable
- آرایه‌ها مانند ([])observable
- Maps مانند observable(new Map())

چند نکته:
- همانطور که در قسمت قبل نیز ذکر شد، decorators در اصل یکسری تابع هستند و برای مثال می‌توان observable را به صورت observable@ و یا به صورت یک تابع معمولی مورد استفاده قرار داد.
- اگر شیء‌ای را به صورت ({})observable معرفی کنیم، با افزودن خواصی به آن پس از این فراخوانی، این خواص دیگر مورد ردیابی قرار نخواهند گرفت. علت آن‌را هم در شبه‌کد زیر می‌توان مشاهده کرد:
const extendObservable = (target, source) => {
  source.keys().forEach(key => {
    const wrappedInObservable = observable(source[key]);
    Object.defineProperty(target, key, {
      set: value.set.
      get: value.get
    });
  });
};
کاری که متد observable انجام می‌دهد، شمارش کلیدهای (خواص) شیء ارسالی به آن است و سپس محصور کردن آن‌ها درون یک شیء observable و در آخر بازگشت آن.
برای رفع این مشکل می‌توان از Map استفاده کرد. یعنی در اینجا اگر قرار است تعداد خواص اشیاء را به صورت پویا تغییر دهید، آن‌ها را به صورت Map تعریف کنید؛ چون متد set آن توسط observableها ردیابی می‌شود.


استفاده از MobX با React توسط کتابخانه‌ی mobx-react

تا اینجا MobX را به صورت متکی به خود مورد بررسی قرار دادیم. اکنون قصد داریم آن‌را به یک برنامه‌ی React متصل کنیم. برای اینکار کتابخانه‌های زیادی وجود دارند که در این قسمت کلیات روش کار با کتابخانه‌ی mobx-react را در بین آن‌ها بررسی می‌کنیم.

نصب کتابخانه‌ی mobx-react

ابتدا نیاز است تا این کتابخانه را نصب کنیم:
 > npm install --save mobx mobx-react

تحت نظر قرار دادن کامپوننت‌ها

در ادامه پس از نصب کتابخانه‌ی mobx-react، نیاز است کامپوننت‌ها را تحت نظر MobX قرار دهیم که اینکار را می‌توان توسط تزئین کننده‌ی observer آن انجام داد. همانطور که عنوان شد، تزئین کننده‌ها را می‌توان به صورت معمولی observer@ به یک کلاس و یا به صورت فراخوانی تابع، برای مثال به یک کامپوننت تابعی اعمال کرد. برای نمونه کامپوننت‌های کلاسی را به نحو زیر می‌توان با observer@ مزین کرد:
import { observer } from "mobx-react";

@observer class Counter extends Component {
در این حالت هر زمانیکه یکی از اشیاء observable تغییر می‌کند، React را وادار به رندر مجدد UI خواهد کرد.

و یا کامپوننت‌های تابعی را می‌توان توسط متد observer به صورت زیر محصور کرد:
const Counter = observer(({ count }) => {
  return (
   // ...
  );
});
با تحت نظر قرار گرفته شدن یک کامپوننت (چه با تزئین کننده‌ی observer@ و یا با بکارگیری نگارش تابعی آن)، منطقی که در پشت صحنه مورد استفاده قرار می‌گیرد، یک چنین شکلی را خواهد داشت (و برای اینکار نیازی به کد نویسی نیست):
class ContainerComponent extends Component () {
   componentDidMount() {
     this.stopListening = autorun(() => this.render());
   }

   componentWillUnmount() {
     this.stopListening();
   }

   render() { … }
}
زمانیکه کار رندر اولیه‌ی کامپوننت در DOM به پایان رسید، متد autorun به تغییرات observableها در پشت صحنه گوش‌فرا داده و سبب فراخوانی متد رندر کامپوننت، با هر تغییر لازمی می‌شود. این کاری است که متد یا تزئین کننده‌ی observer کتابخانه‌ی mobx-react انجام می‌دهد.

تعریف مخزن و اتصال آن به کامپوننت‌ها

کار شیء Provider که بالاترین کامپوننت را در سلسله مراتب کامپوننت‌ها محصور می‌کند، ارائه‌ی store، به تمام کامپوننت‌های فرزند است. در Redux فقط یک store را داریم که  به شیء Provider آن ارسال می‌کنیم. اما در حین کار با MobX چنین محدودیتی وجود ندارد و می‌توان چندین store را تعریف کرد و در اختیار برنامه قرار داد که شبه‌کد نحوه‌ی تعریف آن به صورت زیر است:
import { Provider } from 'mobx-react';

import ItemStore from './store/ItemStore';
import Application from './components/Application';

const itemStore = new ItemStore();

ReactDOM.render(
   <Provider itemStore={itemStore}>
     <Application />
   </Provider>,
   document.getElementById('root'),
);
در حین کار با Redux، قسمتی از مراحل تعریف آن، کار اتصال خواص موجود در state مخزن redux، به props یک کامپوننت است و یا همچنین کار اتصال رویدادها به props. یک چنین کاری را در اینجا به سادگی با تزئین کننده‌ای به نام inject می‌توان انجام داد که مخزن مورد استفاده را مشخص می‌کند:
@inject('itemStore')
class NewItem extends Component {
// ...
و یا برای کامپوننت‌های تابعی می‌توان از نگارش تابعی inject استفاده کرد. در این حالت، store تزریقی را می‌توان به صورت props دریافت نمود:
const UnpackedItems = inject('itemStore')(
    observer(({ itemStore }) => (
    // ...
  )),
);


یک مثال: پیاده سازی مثال شمارشگر قسمت سوم این سری با mobx-react

در ادامه قصد داریم برنامه‌ی شمارشگر ارائه شده در قسمت سوم بررسی redux را با mobx پیاده سازی کنیم. به همین جهت یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app state-management-with-mobx-part2
> cd state-management-with-mobx-part2
> npm start
در ادامه کتابخانه‌ها‌ی mobx ، mobx-react و همچنین بوت استرپ را نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save mobx mobx-react bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";

پس از آن فایل src\index.js را به صورت زیر تغییر می‌دهیم:
import "./index.css";
import "bootstrap/dist/css/bootstrap.css";

import { autorun, decorate, observable } from "mobx";
import React from "react";
import ReactDOM from "react-dom";

import Counter from "./components/Counter";
import * as serviceWorker from "./serviceWorker";

class Count {
  value = 0;

  increment = () => {
    this.value++;
  };

  decrement = () => {
    this.value--;
  };
}

decorate(Count, { value: observable });

const count = (window.count = new Count());
autorun(() => console.log("The count changed!", count.value));

ReactDOM.render(
  <main className="container">
    <Counter count={count} />
  </main>,
  document.getElementById("root")
);

serviceWorker.unregister();
توضیحات:
- در قسمت قبل، روش تحت نظر قرار دادن یک شیء متداول جاوا اسکریپتی را توسط متد observable مشاهده کردیم. در اینجا نگارش کلاسی آن مثال را بر اساس کلاس Count مشاهده می‌کنید. اگر نخواهیم از decorator ای مانند observable@ بر روی خاصیت value این کلاس استفاده کنیم، روش تابعی آن‌را با فراخوانی متد decorate و ذکر نوع کلاس و سپس خاصیتی که باید به صورت observable تحت نظر قرار گیرد، در اینجا مشاهده می‌کنید. این هم یک روش کار با mobx است.
- پس از ایجاد کلاس Count که در اینجا نقش store را نیز بازی می‌کند، یک وهله‌ی جدید را از آن ساخته و به متغیر count در این ماژول و همچنین window.count انتساب می‌دهیم. انتساب window.count سبب می‌شود تا بتوان در کنسول توسعه دهندگان مرورگر، با نوشتن count و سپس enter، به محتویات این متغیر دسترسی یافت و یا حتی آن‌را تغییر داد؛ مانند تصویر زیر که بلافاصله این تغییر، در UI برنامه نیز منعکس می‌شود:


- در اینجا تعریف شیء Provider را که پیشتر در مورد آن بحث کردیم، مشاهده نمی‌کنید؛ چون با تک کامپوننت Counter تعریف شده‌ی در این مثال، نیازی به آن نیست. می‌توان این شیء store را به صورت مستقیم به props کامپوننت Counter ارسال کرد.

اکنون تعریف کامپوننت شمارشگر واقع در فایل src\components\Counter.jsx به صورت زیر خواهد بود که این کامپوننت، count را به صورت props دریافت می‌کند:
import { observer } from "mobx-react";
import React from "react";

const Counter = observer(({ count }) => {
  return (
    <section className="card mt-5">
      <div className="card-body text-center">
        <span className="badge m-2 badge-primary">{count.value}</span>
      </div>
      <div className="card-footer">
        <div className="d-flex justify-content-center align-items-center">
          <button
            className="btn btn-secondary btn-sm"
            onClick={count.increment}
          >
            +
          </button>
          <button
            className="btn btn-secondary btn-sm m-2"
            onClick={count.decrement}
          >
            -
          </button>
        </div>
      </div>
    </section>
  );
});

export default Counter;
و سپس بر اساس count رسیده، در اینجا می‌توان مستقیما متدهای کلاس Count را فراخوانی کرد (مانند count.increment؛ که البته در اصل یک خاصیت است که با متدی مقدار دهی شده‌است) و یا مقدار خاصیتی از آن‌را مانند count.value، نمایش داد.
تا زمانیکه کامپوننت، با تابع observer محصور شده‌است، به props رسیده گوش فرا داده و خواص و اشیاء observable آن‌را تشخیص می‌دهد و سبب رندر مجدد کامپوننت، با تغییری در آن‌ها خواهد شد.

کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید: state-management-with-mobx-part2.zip
مطالب
مروری بر کتابخانه ReactJS - قسمت هشتم - آخرین قسمت - چرخه حیات کامپوننت‌ها

هر کامپوننتی در React یک چرخه زندگی دارد. زمانیکه یک کامپوننت را به روش React.createClass یا React.Component تعریف میکنیم و در ReactDOM.render نمونه‌ای از کامپوننت را برای نمایش در مرورگر می‌سازیم، چرخه حیات آن شروع میشود. 


ReactDOMServer

کتابخانه ReactDOMServer جهت ساخت یا render کردن کامپوننت‌ها در سمت سرور استفاده میشود. توسط این کتابخانه میتوانیم کامپوننت‌ها را در سمت سرور ایجاد کنیم و نتیجه آن را که تگ‌های HTML هستند به مرورگر ارسال کنیم. این روش جهت داشتن صفحه‌های وب سریع‌تر و اهداف SEO مفید است. جهت اطلاعات بیشتر و روش‌های استفاده به مستندات آن رجوع کنید. در مثال زیر روش استفاده از این کتابخانه به اختصار آمده.

var persons = [
    { id: 1, personName: "Parham", personContact: "parhamda@gmail.com" },
    { id: 2, personName: "Roham", personContact: "roham@yahoo.com" },
    { id: 3, personName: "Raha", personContact: "raha@live.com" }
];

class Person extends React.Component{
    render(){
        return (
            <div>
                <p>{this.props.personName}</p>
                <p>{this.props.personContact}</p>
            </div>
        )
    }
}

let person1 = persons[0];
let personElement = <Person personName={person1.personName} personContact={person1.personContact}/>
console.log(ReactDOMServer.renderToStaticMarkup(personElement));

در کد بالا مواردی که جدید هستند، یکی ساخت یک نمونه از کامپوننت Person است و دیگری ساخت آن در سمت سرور، بدون آن که فعلا نمایشی در مرورگر داشته باشیم. در کنسول میتوانیم خروجی کتابخانه را که تگ‌های HTML هستند ببینیم. ReactDOMServer دو متد را فراهم کرده که کارکردی مشابه دارند؛ اما در جزئیات متفاوت هستند. 

  • renderStaticMarkup یک خروجی استاتیک و بدون attributeهای اضافه را تولید میکند که بیشتر برای بررسی یا استفاده در صفحه‌های وب ایستا مفید هستند.
  • renderToString یک خروجی به صورت HTML String ایجاد میکند که برای HTML DOM در سمت کاربر سازگار‌تر است و مناسب برای صفحات پویا. 

در نهایت خروجی از هر نوع که بود، برای اینکه در سمت کاربر قابل مشاهده باشد باید از همان متد ReactDOM.render استفاده کنیم. از آنجایی که این مجموعه جهت معرفی و بررسی ابزارهای اصلی React به صورت مختصر است، از آوردن مثال‌های زیاد و پیچیده پرهیز میکنم. در اینجا میتوانید یک نمونه ساده برای استفاده از ReactDOMServer به صورت استاندارد و با جزئیات را بررسی کنید.


متدهای چرخه حیات در React

React چند متد را برای زمان‌های قبل و بعد از ساخت شدن یک کامپوننت در DOM دارد که میشود رفتارهایی را برای کامپوننت، در این متدها در نظر گرفت تا در زمان مناسب اجرا شوند. در ادامه این متد‌ها معرفی و کاربرد هر یک بیان میشود. 


 componentWillMount: این متد قبل از اینکه کامپوننت، تگ‌های متد render را بسازد اجرا میشود. این متد هم در سمت کلاینت کاربرد دارد و هم در سمت سرور. به همین جهت برای گرفتن log از داده‌های کامپوننت و کار با پایگاه داده مکان مناسبی است. به عنوان مثال در قطعه کد زیر داده‌های کامپوننت، توسط Ajax ارسال شده‌اند. 

componentWillMount() {
   Ajax.post("/componentLog", {
     name: this.constructor.name,
     props: this.props
   });
}

componentDidMount: این متد بعد از اینکه بخش render اجرا شد فراخوانی میشود. همچنین فقط در سمت کلاینت و زمانیکه از ReactDOM.render استفاده میکنیم کاربرد دارد. این متد مناسب برای تعامل کامپوننت با افزونه‌ها و API‌ها است؛ مانند دریافت اطلاعات مورد نیاز کامپوننت از سایتی دیگر توسط یک API. از  این متد در قسمت چهارم مثالی آورده شده. 


(componentWillReciveProps(nextProps: این متد زمانی اجرا میشود که داده‌های ورودی کامپوننت با مقادیری جدید تغییر کنند.

componentWillReceiveProps(nextProps) {
    // Do something with new received data and change the state. 
}

ReactDOM.render(
    <TestComponent someData={newDataEveryFiveSecond()}/>,
    document.getElementById("divTest")
);

در مثال بالا یک کامپوننت داریم که داده‌های ورودی خود را از یک تابع میگیرد. این تابع هر پنج ثانیه یک بار یک داده تازه ایجاد میکند و به کامپوننت ارسال میکند. میتوانیم داخل کامپوننت، از متد componentWillReceiveProps جهت دستکاری داده‌های رسیده و تغییر وضعیت کامپوننت توسط setState استفاده کنیم. 


(shouldComponentUpdate(nextProps, nextState: این متد شبیه به متد componentWillReceiveProps است، البته با تفاوت‌هایی. این متد هم مقدار ورودی جدید برای پارامتر‌های کامپوننت میگیرد و هم مقداری برای وضعیتی که کامپوننت دارد. این متد باید یک مقدار بازگشتی false یا  true داشته باشد. با این مقدار بازگشتی میتوان کنترل کرد که آیا کامپوننت بر اساس داده‌های جدید بروز بشود یا نه. 

class ComponentExample extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return notEqual(this.props, nextProps) ||
            notEqual(this.state, nextState);
    }
}

در مثال بالا پارامترها و وضعیت جاری کامپوننت، با مقدارهای تازه تغییر یافته و وضعیت جدید مقایسه میشوند. اگر مقادیر مقایسه شده برابر نباشند (یعنی داده تکراری وارد نشده) مقدار بازگشتی true خواهد بود و React کامپوننت را بر اساس وضعیت جدید و داده‌های تازه دوباره میسازد.


(componentWillUpdate(nextProps, nextState: این متد زمانیکه کامپوننت ساخته شده، داده‌های جدیدی را دریافت کند و یا وضعیت آن تغییر کند و دقیقا قبل از اجرای render فراخوانی میشود. اگر از متد shouldComponentUpdate مقدار false بازگشت داده شود، این متد دیگر اجرا نخواهد شد. باید توجه داشته باشیم که setState را نمیشود در این متد پیاده‌سازی کرده. به این علت که، زمانیکه وضعیت کامپوننت تغییر میکند، React متد componentWillUpdate و بلافاصله بعد از آن render را اجرا میکند و برای تغییر وضعیت دیگر دیر شده! تفاوت componentWillUpdate با componentWillMount  این است که Will Mount در اولین وهله سازی از کامپوننت اجرا میشود، ولی Will Update بعد از هر دوباره سازی (rerender). 


(componentDidUpdate(prevProps, prevStat: احتمالا میشود به راحتی حدس زد که این متد دقیقا بعد از دوباره سازی کامپوننتی که ساخته شده فراخوانی میشود.


componentWillUnmount: این متد زمانی اجرا میشود که یک کامپوننت از DOM پاک شود. برای پاک کردن نمونه‌ای از یک کامپوننت که در DOM در حال نمایش است میتوانیم از دستور زیر استفاده کنیم. 

ReactDOM.unmountComponentAtNode(document.getElementById("react"));
مطالب دوره‌ها
افزونه‌ای برای کپسوله سازی نکات ارسال یک فرم ASP.NET MVC به سرور توسط jQuery Ajax
اگر مطالب سایت جاری را مطالعه و دنبال کرده باشید، تاکنون به صورت پراکنده نکات زیادی را در مورد استفاده از jQuery Ajax تهیه و ارائه کرده‌ایم. در این مطلب قصد داریم تا این نکات را نظم بخشیده و جهت استفاده مجدد، به صورت یک افزونه کپسوله سازی کنیم.

در کدها و افزونه‌ای که در ادامه ارائه خواهند شد، این مسایل درنظر گرفته شده است:

- چگونه اعتبار سنجی سمت کاربر را در حین استفاده از Ajax فعال کنیم.
- چگونه از چندبار کلیک کاربر در حین ارسال فرم به سرور جلوگیری نمائیم.
- چگونه Complex Types قابل تعریف در EF Code first را نیز در اینجا مدیریت کنیم.
- نحوه تعریف صحیح آدرس‌های کنترلرها چگونه باید باشد.
- نحوه اعلام وضعیت لاگین شخص به او، در صورت بروز مشکل.
- ارسال صحیح anti forgery token در حین اعمال Ajax ایی.
- بررسی Ajax بودن درخواست رسیده و تهیه یک فیلتر سفارشی مخصوص آن.
- از کش شدن اطلاعات Ajax ایی جلوگیری شود.


ابتدا معرفی مدل برنامه
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace jQueryMvcSample01.Models
{
    public class User
    {
        [Required(ErrorMessage = "(*)"), DisplayName("نام")]
        public string Name { set; get; }

        public PhoneInfo PhoneInfo { set; get; }
    }

    public class PhoneInfo
    {
        [Required(ErrorMessage = "(*)"), DisplayName("تلفن")]
        public string Phone { get; set; }

        [Required(ErrorMessage = "(*)"), DisplayName("پیش شماره")]
        public string Ext { get; set; }
    }
}
همانطور که ملاحظه می‌کنید، خاصیت PhoneInfo، تو در تو یا به نوعی Complex است. اگر از ابزارهای Scafolding توکار VS.NET برای تولید View متناظر استفاده کنیم، فیلد تو در توی PhoneInfo را لحاظ نخواهد کرد، اما ... مهم نیست. تعریف دستی آن هم کار می‌کند.


کدهای کنترلر برنامه

using System.Web.Mvc;
using jQueryMvcSample01.Models;
using jQueryMvcSample01.Security;

namespace jQueryMvcSample01.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش فرم
        }

        [HttpPost]
        [AjaxOnly] //فقط در حالت ای‌جکس قابل دسترسی باشد
        [ValidateAntiForgeryToken]
        public ActionResult Index(User user)
        {
            if (this.ModelState.IsValid)
            {
                // ذخیره سازی در بانک اطلاعاتی ...
                System.Threading.Thread.Sleep(3000);

                return Content("ok");//اعلام موفقیت آمیز بودن کار
            }

            return Content(null);//ارسال خطا
        }
    }
}
در اینجا در متد Index، اطلاعات شیء User به صورت Ajaxایی دریافت شده و پس از آن برای مثال قابلیت ذخیره سازی را خواهد داشت.
چند نکته در اینجا حائز اهمیت هستند:
الف) استفاده از ویژگی AjaxOnly (که کدهای آن‌را در پروژه پیوست می‌توانید مشاهده نمائید)، جهت صرفا پردازش درخواست‌های Ajaxایی.
ب) استفاده از ویژگی ValidateAntiForgeryToken در حین اعمال اجکسی. اگر سایت‌های مختلف را در اینباره جستجو کنید، عموما برای پردازش آن در حین استفاده از jQuery Ajax بسیار مشکل دارند.
ج) استفاده از return Content برای اعلام نتیجه کار. اگر اطلاعات ثبت شد، یک ok یا هر عبارت دیگری که علاقمند بودید ارسال گردیده و در غیراینصورت null بازگشت داده می‌شود.


کدهای افزونه PostMvcFormAjax

// <![CDATA[
(function ($) {
    $.fn.PostMvcFormAjax = function (options) {
        var defaults = {
            postUrl: '/',
            loginUrl: '/login',
            beforePostHandler: null,
            completeHandler: null,
            errorHandler: null
        };
        var options = $.extend(defaults, options);

        var validateForm = function (form) {
            //فعال سازی دستی اعتبار سنجی جی‌کوئری
            var val = form.validate();
            val.form();
            return val.valid();
        };

        return this.each(function () {
            var form = $(this);
            //اگر فرم اعتبار سنجی نشده، اطلاعات آن ارسال نشود
            if (!validateForm(form)) return;

            //در اینجا می‌توان مثلا دکمه‌ای را غیرفعال کرد
            if (options.beforePostHandler)
                options.beforePostHandler(this);

            //اطلاعات نباید کش شوند
            $.ajaxSetup({ cache: false });

            $.ajax({
                type: "POST",
                url: options.postUrl,
                data: form.serialize(), //تمام فیلدهای فرم منجمله آنتی فرجری توکن آن‌را ارسال می‌کند
                complete: function (xhr, status) {
                    var data = xhr.responseText;
                    if (xhr.status == 403) {
                        window.location = options.loginUrl; //در حالت لاگین نبودن شخص اجرا می‌شود
                    }
                    else if (status === 'error' || !data) {
                        if (options.errorHandler)
                            options.errorHandler(this);
                    }
                    else {
                        if (options.completeHandler)
                            options.completeHandler(this);
                    }
                }

            });
        });
    };
})(jQuery);
// ]]>
چند نکته مهم در تهیه این افزونه رعایت شده:
الف) فعال سازی دستی اعتبار سنجی جی‌کوئری، از این جهت که این نوع اعتبار سنجی به صورت پیش فرض تنها در حالت postback و ارسال کامل صفحه به سرور فعال می‌شود.
ب) استفاده از متد serialize جهت پردازش یکباره کل اطلاعات و فیلدهای یک فرم.
نکته مهم این متد ارسال فیلد مخفی anti forgery token نیز می‌باشد. فقط باید دقت داشت که این فیلد در حالتی که dataType به json تنظیم شود و همچنین از متد serialize استفاده گردد، در ASP.NET MVC پردازش نمی‌گردد (خیلی مهم!). به همین جهت در اینجا dataType تنظیمات jQuery Ajax حذف شده است.
ج) تنظیم cache به false در تنظیمات ابتدایی jQuery Ajax تا اطلاعات ارسالی و دریافتی کش نشوند و مشکل ساز نگردند.
د) بررسی xhr.status == 403 که توسط SiteAuthorizeAttribute (جایگزین بهتر فیلتر Authorize توکار ASP.NET MVC که کدهای آن در پروژه پیوست قابل دریافت است) و هدایت کاربر به صفحه لاگین


تعریف View ایی که از اشیاء تو در تو استفاده می‌کند و همچنین از افزونه فوق برای ارسال اطلاعات بهره خواهد برد:

@model jQueryMvcSample01.Models.User
@{
    ViewBag.Title = "تعریف کاربر";
    var postUrl = Url.Action(actionName: "Index", controllerName: "Home");
}
@using (Html.BeginForm(actionName: "Index", controllerName: "Home",
                       method: FormMethod.Post,
                       htmlAttributes: new { id = "UserForm" }))
{
    @Html.ValidationSummary(true)
    @Html.AntiForgeryToken()

    <fieldset>
        <legend>تعریف کاربر</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.PhoneInfo.Ext)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PhoneInfo.Ext)
            @Html.ValidationMessageFor(model => model.PhoneInfo.Ext)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.PhoneInfo.Phone)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.PhoneInfo.Phone)
            @Html.ValidationMessageFor(model => model.PhoneInfo.Phone)
        </div>
        <p>
            <input type="submit" id="btnSave" value="ارسال" />
        </p>
    </fieldset>
}
@section JavaScript
{
    <script type="text/javascript">
        $(document).ready(function () {
            $("#btnSave").click(function (event) {
                //جلوگیری از پست بک به سرور
                event.preventDefault();

                var button = $(this);

                $("#UserForm").PostMvcFormAjax({
                    postUrl: '@postUrl',
                    loginUrl: '/login',
                    beforePostHandler: function () {
                        //غیرفعال سازی دکمه ارسال
                        button.attr('disabled', 'disabled');
                        button.val("...");
                    },
                    completeHandler: function () {
                        //فعال سازی مجدد دکمه ارسال
                        alert('انجام شد');
                        button.removeAttr('disabled');
                        button.val("ارسال");
                    },
                    errorHandler: function () {
                        alert('خطایی رخ داده است');
                    }
                });
            });
        });
    </script>
}
همانطور که عنوان شد، مهم نیست که اشیاء تو در تو توسط ابزار Scafolding پشتیبانی نمی‌شود. این نوع خواص را به همان نحو متداول ذکر زنجیره وار خواص می‌توان معرفی و استفاده کرد:
 @Html.EditorFor(model => model.PhoneInfo.Phone)
هم اعتبار سنجی سمت کلاینت آن کار می‌کند و هم اطلاعات آن به اشیاء و خواص متناظر به خوبی نگاشت خواهد شد:


در ادامه نحوه استفاده از افزونه PostMvcFormAjax را مشاهده می‌کنید. چند نکته نیز در اینجا حائز اهمیت هستند:
الف) توسط htmlAttributes یک id برای فرم تعریف کرده‌ایم تا در افزونه PostMvcFormAjax مورد استفاده قرار گیرد.
ب) postUrl و loginUrl را همانند متغیر تعریف شده در ابتدای View توسط Url.Action باید تعریف کرد تا در صورتیکه سایت ما در ریشه اصلی قرار نداشت، باز هم به صورت خودکار مسیر صحیحی محاسبه و ارائه گردد.
ج) نحوه غیرفعال سازی و فعال سازی دکمه submit را در روال‌های beforePostHandler و completeHandler ملاحظه می‌کنید. این مساله برای جلوگیری از کلیک‌های مجدد یک کاربر ناشکیبا و جلوگیری از ثبت اطلاعات تکراری بسیار مهم است.
د) کل این اطلاعات، در یک section به نام JavaScript ثبت شده است. این section در فایل layout برنامه به صورت زیر مورد استفاده قرار خواهد گرفت و به این ترتیب مقدار دهی خواهد شد:
<head>
    <title>@ViewBag.Title</title>    
    <link href="@Url.Content("Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.PostMvcFormAjax.js")" type="text/javascript"></script>
    @RenderSection("JavaScript", required: false)
</head>

دریافت کدهای کامل این قسمت
jQueryMvcSample01.zip

 
مطالب
تشخیص نقایص تصاویر صفحات سایت با استفاده از jQuery Ajax

این مثال شبیه به مثال بررسی وجود نام کاربر با استفاده از jQuery Ajax است که از ذکر توضیحات مشابه آن، در اینجا خودداری خواهد شد.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestBrokenImages.aspx.cs"
Inherits="testWebForms87.TestBrokenImages" %>

<!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>detecting broken images</title>

<script src="jquery.min.js" type="text/javascript"></script>

<script type="text/javascript">
function errorReplace(arg) {
//ارسال پیغام خطا
$.ajax({
type: "POST",
url: "TestBrokenImages.aspx/GetErros",
data: "{'image': '" + arg.src + "','page':'" + location.href + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json"
});
//نمایش تصویری دلخواه بجای نمونه مفقود
$(arg).attr('src', 'missing.png');
}

//بررسی وضعیت تک تک تصاویر پس از بارگذاری کامل صفحه
$(document).ready(function() {
$(window).bind('load', function() {
$('img').each(function() {
if (!this.complete || (!$.browser.msie && (typeof this.naturalWidth == "undefined" || this.naturalWidth == 0))) {
errorReplace(this);
}
});
})
});
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
<img src="img1.png" />
<img src="img2.png" />
</div>
</form>
</body>
</html>

using System;
using System.IO;
using System.Web.Services;

namespace testWebForms87
{
public partial class TestBrokenImages : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

[WebMethod]
public static void GetErros(string image,string page)
{
//ارسال ایمیل به مسؤول سایت و یا ذخیره خطاها در دیتابیس
}
}
}

در این مثال زمانیکه صفحه کاملا بارگذاری شد، وضعیت تک تک تصاویر بررسی می‌شود، اگر تصویر مفقودی وجود داشت (با اکثر مرورگرها سازگار است)، اطلاعات آن به تابع errorReplace ارسال خواهد شد.
در این تابع با استفاده از jQuery Ajax ، اطلاعات تصویر مفقود و صفحه مربوطه به وب متد GetErros ما ارسال می‌شود. سپس در این متد می‌توان یا آرگومان‌های دریافتی را به صورت یک ایمیل به مسؤول سایت ارسال نمود و یا آن‌ها را جهت بررسی آتی در یک دیتابیس ذخیره کرد.
بدیهی است بجای قرار دادن وب متد فوق در صفحه جاری، می‌توان یک وب سرویس را نیز ایجاد و متد را در آن قرار داد تا نیازی نباشد به ازای هر صفحه سایت یکبار این متد تکرار شود.

اگر موفق به اجرای این مثال نشدید، برای مثال یک break point داخل متد GetErrors قرار دهید و برنامه را در حالت دیباگ در ویژوال استودیو شروع کنید، اگر اتفاق خاصی رخ نداد و به این break point نرسیدید، احتمالا تنظیمات وب کانفیگ شما مناسب نیست. قسمت مربوط به system.web.extensions ، webServices و jsonSerialization باید در وب کانفیگ موجود باشد که VS 2008 این موارد را به صورت خودکار اضافه می‌کند.

مطالب
بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net

شاید بعضی از سایت‌ها را دیده باشید که در حین ثبت نام، پس از وارد کردن یک نام کاربری و سپس مشغول شدن به پر کردن فیلد کلمه‌ی عبور، در قسمت نام کاربری شروع به جستجو در مورد آزاد بودن نام کاربری درخواستی می‌کنند یا نمونه‌ای دیگر، فرم پرداخت الکترونیکی بانک سامان. پس از اینکه شماره قبض را وارد کردید، بلافاصله بدون ریفرش صفحه به شما پیغام می‌دهد که این شماره معتبر است یا خیر. امروز قصد داریم این قابلیت را با استفاده از کتابخانه‌ی Ajax مجموعه jQuery در ASP.Net پیاده سازی کنیم (بدون استفاده از ASP.Net Ajax مایکروسافت).
ابتدا سورس کامل را ملاحظه نمائید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AjaxTest.aspx.cs" Inherits="testWebForms87.AjaxTest" %>
<!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>jQuery Ajax Text</title>

<script src="jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#<%= TextBox1.ClientID %>").blur(function(event) {
$.ajax({
type: "POST",
url: "AjaxTest.aspx/IsUserAvailable",
data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
$('#valid').html("<img src='ajaxImages/waiting.gif' alt='لطفا کمی تامل کنید'>");
var delay = function() {
AjaxSucceeded(msg);
};

setTimeout(delay, 2000); //remove this
},
error: AjaxFailed
});
});
});
function AjaxSucceeded(result) {
if (result.d == true)
$('#msg').html("<img src='ajaxImages/available.gif' alt='نام کاربری درخواستی موجود است'>");
else
$('#msg').html("<img src='ajaxImages/taken.gif' alt='متاسفانه نام کاربری مورد نظر پیشتر دریافت شده‌است'>");
}
function AjaxFailed(result) {
alert(result.status + ' ' + result.statusText);
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
user name:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<span id="msg"></span>
<br />
pass:
<asp:TextBox ID="TextBox2" TextMode="Password" runat="server"></asp:TextBox>
</div>
<!-- preload -->
<div style="display: none">
<img src="ajaxImages/available.gif" alt="available" />
<img src="ajaxImages/taken.gif" alt="taken" />
<img src="ajaxImages/waiting.gif" alt="waiting" />
</div>
</form>
</body>
</html>


using System;
using System.Web.Services;

namespace testWebForms87
{
public partial class AjaxTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

[WebMethod]
public static bool IsUserAvailable(string username)
{
// این مورد را با خواندن اطلاعات از دیتابیس می‌شود تعویض کرد
return username != "test";
}
}
}

توضیحات:
همانطور که ملاحظه می‌کنید صفحه‌ی ASP.Net ما بسیار ساده است و از دو تکست باکس استاندارد تشکیل می‌شود، به همراه تصاویر مربوط به Ajax که یک سری تصاویر ساده چرخان معروف منتظر بمانید ، یافت شد یا موجود نیست می‌باشند. این تصاویر در یک div مخفی (display: none) در صفحه قرار گرفته‌اند و در هنگام بارگذاری صفحه، این‌ها نیز بارگذاری شده و حاضر و آماده خواهند بود. بنابراین هنگام استفاده از آن‌ها، کاربر تاخیری را مشاهده نخواهد کرد. همچنین یک span با id مساوی msg‌ را هم پس از تکست باکس اضافه کرده‌ایم تا تصاویر مربوط به رخدادهای Ajax را با استفاده از توانایی‌های jQuery به آن اضافه کنیم.

اسکریپت Ajax ما با دراختیار گرفتن روال رخداد گردان blur شیء textBox1 شروع می‌شود. همانطور که در مقالات پیشین سایت نیز ذکر شد، روش صحیح دریافت ID یک کنترل ASP.Net در کدهای سمت کلاینت جاوا اسکریپتی، بر اساس خاصیت ClientID آن است که در اولین سطر کدهای ما مشخص است (زیرا در ASP.Net نام و ID یک کنترل در هنگام رندر شدن به همراه ID کنترل‌های دربرگیرنده آن نیز خواهد بود، بنابراین بهتر است این مورد را داینامیک کرد).

کار بررسی موجود بودن نام کاربری (یا مثلا یک شماره قبض و امثال آن) توسط WebMethod ایی به نام IsUserAvailable در code behind صفحه انجام می‌شود که پیاده سازی آن‌را ملاحظه می‌کنید. بدیهی است در این مثال ساده، تنها نام کاربری از پیش رزرو شده، کلمه‌ی test است و در یک کد واقعی این مورد با مقایسه‌ی نام کاربری با اطلاعات موجود در دیتابیس باید صورت گیرد (و حملات تزریق اس کیوال را هم فراموش نکنید. برای رهایی از آن‌ها "حتما" باید از پارامترهای ADO.Net استفاده کرد و گرنه کد شما مستعد به این نوع حملات خواهد بود).

سؤال: چرا از web method استفاده شد و همچنین چرا این متد static است؟
زمانیکه یک متد با کلمه کلیدی static‌ مشخص می‌شود حالت state less پیدا می‌کند یعنی مستقل از وهله‌ی کلاس عمل می‌کند. در این حالت نیازی به ارسال ViewState نبوده (بنابراین در کوئری مورد نظر ما بسیار بهینه و سبک عمل می‌کنند) و همچنین نیازی به ایجاد یک وهله‌ای از کلاس صفحه‌ی ما نیز نخواهد بود. برای توضیحات بیشتر به این مقاله مراجعه نمائید. (به صورت خلاصه، دلیل اصلی، کارآیی بالا و بهینه بودن این روش در این مساله ویژه است و در ASP.Net Ajax مایکروسافت به صورت گسترده‌ای در پشت صحنه مورد استفاده قرار می‌گیرد)
استفاده از ویژگی WebMethod عملکرد صفحه‌ی ما را شبیه به یک وب سرویس خواهد کرد و امکان دسترسی به آن در متدهای استاندارد POST به صورت ارسال دیتا به آدرس WebService.asmx/WebMethodName‌ خواهد بود. یک مثال ساده و عملی

بررسی تابع Ajax بکار رفته:
این تابع هنگام فراخوانی رخداد blur تکست‌باکس ما (مطابق کد فوق) فراخوانی می‌شود. ساختار ساده‌ای دارد که به شرح زیر است:
type: "POST"
نحوه‌ی ارسال داده را به متد وب سرویس ما مشخص می‌کند.

url: "AjaxTest.aspx/IsUserAvailable"
اطلاعات پست شده، به صفحه‌ی AjaxTest.aspx و وب متد IsUserAvailable ارسال خواهد شد

data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
داده‌ای که به آرگومان username ما ارسال می‌شود، همان مقدار تایپ شده در TextBox1 است (که باز هم دریافت ID آن به صورت داینامیک صورت گرفته تا مشکل زا نشود)

contentType: "application/json; charset=utf-8",
dataType: "json",
در این دو سطر از نوع داده‌ی json استفاده شده است که فرمت بسیار سبک و بهینه‌ای برای تبادل اطلاعات در وب به‌شمار می‌آید و توسط کتابخانه‌های جاوا اسکریپتی به سادگی پردازش شده و تبدیل به اشیاء مورد نظر خواهند شد. برای مثال اگر خروجی یک وب سرویس در حالت xml به صورت زیر باشد:

<xx yy="nn"></xx>
معادل json آن به شرح زیر است:
{ "xx": {"yy":"nn"} }
success: function(msg)
Success ، پس از موفقیت آمیز بودن عملیات ajax در jQuery فراخوانی می‌شود
error: AjaxFailed
و اگر در این بین خطایی رخ داده باشد، قسمت error فراخوانی می‌شود.

در این مثال برای نمایش بهتر عملیات، یک وقفه‌ی 2 ثانیه‌ای توسط setTimeout ایجاد شده و بدیهی است در یک مثال واقعی باید آن‌را حذف نمود.

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





نظرات مطالب
مروری بر تاریخچه محدودیت حافظه مصرفی برنامه‌های ASP.NET در IIS
بستگی داره Session state به چه صورتی تنظیم شده باشد. می‌شود آن‌را طوری تنظیم کرد که در اس کیوال سرور هم حتی ذخیره شود. حالت InProc آن یعنی همان توضیحات فوق و تمام تنظیمات app pool به آن اعمال می‌شود. اطلاعات بیشتر:
Session State
مطالب دوره‌ها
به روز رسانی غیرهمزمان قسمتی از صفحه به کمک jQuery در ASP.NET MVC
یک صفحه شلوغ و سنگین را در نظر بگیرید. برای مثال قرار است ابتدا مطلب خاصی در سایت نمایش یابد و سپس ادامه صفحه که شامل انبوهی از لیست نظرات مرتبط با آن مطلب است به صورت غیرهمزمان و Ajax ایی بدون توقف پردازش صفحه، در فرصتی مناسب از سرور دریافت و به صفحه اضافه گردد (به روز رسانی قسمتی از صفحه در فرصت مناسب). در این حالت چون نمایش اولیه صفحه سریع صورت می‌گیرد، کاربر نهایی آنچنان احساس کند بودن بازکردن صفحات سایت را نخواهد داشت. در ادامه نحوه پیاده سازی این روش را به کمک jQuery Ajax بررسی خواهیم کرد.

مدل و کنترلر برنامه

namespace jQueryMvcSample07.Models
{
    public class BlogPost
    {
        public int Id { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }
}

using System.Web.Mvc;
using System.Web.UI;
using jQueryMvcSample07.Models;
using jQueryMvcSample07.Security;

namespace jQueryMvcSample07.Controllers
{
    public class HomeController : Controller
    {
        [HttpGet]
        public ActionResult Index()
        {
            return View(); //نمایش یک منوی ساده در ابتدای کار
        }

        [HttpGet]
        public ActionResult ShowSynchronous()
        {
            var model = getModel();
            return View(model); //نمایش همزمان
        }

        private static BlogPost getModel()
        {
            //شبیه سازی یک عملیات طولانی
            System.Threading.Thread.Sleep(3000);
            var model = new BlogPost
            {
                Title = "عنوان ... ",
                Body = "مطلب... "
            };
            return model;
        }

        [HttpGet]
        public ActionResult ShowAsynchronous()
        {
            return View(); //نمایش ابتدایی صفحه
        }

        [HttpPost]
        [AjaxOnly]
        [OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
        public ActionResult RenderAsynchronous()
        {
            //دریافت اطلاعات به صورت غیرهمزمان
            var model = getModel();
            return PartialView(viewName: "_Post", model: model);
        }
    }
}
مدل برنامه، بیانگر ساختار اطلاعات مطلبی است که قرار است نمایش یابد.
در کنترلر Home، ابتدا اکشن متد Index آن فراخوانی شده و در این حالت دو لینک زیر نمایش داده می‌شوند:
@{
    ViewBag.Title = "Index";
}
<h2>
    نمایش اطلاعات به صورت همزمان و غیرهمزمان</h2>
<ul>
    <li>
        @Html.ActionLink(linkText: "نمایش همزمان", actionName: "ShowSynchronous", controllerName: "Home")
    </li>
    <li>
        @Html.ActionLink(linkText: "نمایش غیر همزمان", actionName: "ShowAsynchronous", controllerName: "Home")
    </li>
</ul>
لینک اول، به اکشن متد ShowSynchronous اشاره می‌کند و لینک دوم به اکشن متد ShowAsynchronous.
در هر دو صفحه نهایتا از یک Partial View به نام _Post.cshtml برای نمایش اطلاعات استفاده خواهد شد:
@model jQueryMvcSample07.Models.BlogPost
<fieldset>
    <legend>@Model.Title</legend>
    @Model.Body
</fieldset>
زمانیکه کاربر بر روی لینک نمایش همزمان کلیک می‌کند، به صفحه زیر هدایت می‌شود:
@model jQueryMvcSample07.Models.BlogPost
@{
    ViewBag.Title = "ShowSynchronous";
}

<h2>نمایش همزمان</h2>
@{ Html.RenderPartial("_Post", Model); }
این صفحه، یک صفحه متداول است و اطلاعات آن دقیقا در زمان نمایش صفحه اخذ شده و چون در اینجا از یک Sleep عمدی برای تولید اطلاعات استفاده گردیده است، نمایش آن حداقل سه ثانیه طول خواهد کشید.
در حالتیکه کاربر بر روی لینک نمایش غیرهمزمان کلیک می‌کند، صفحه زیر را مشاهده خواهد کرد:
@{
    ViewBag.Title = "ShowAsynchronous";
    var loadInfoUrl = Url.Action(actionName: "RenderAsynchronous", controllerName: "Home");
}
<h2>
    نمایش غیر همزمان</h2>
<div id="info" align="center">
</div>
<div id="progress" align="center" style="display: none">
    <br />
    <img src="@Url.Content("~/Content/images/loadingAnimation.gif")" alt="loading..."  />
</div>
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#progress").css("display", "block");
            $.ajax({
                type: "POST",
                url: '@loadInfoUrl',
                complete: function (xhr, status) {
                    var data = xhr.responseText;
                    if (xhr.status == 403) {
                        window.location = "/login";
                    }
                    else if (status === 'error' || !data || data == "nok") {
                        alert('خطایی رخ داده است');
                    }
                    else {
                        $("#progress").css("display", "none");
                        $("#info").html(data);
                    }
                }
            });
        });
    </script>
}
نمایش ابتدایی این صفحه بسیار سریع است. در ابتدای کار progress bar ایی فعال شده و سپس از طریق jQuery Ajax درخواست دریافت اطلاعات رندر شده اکشن متدی به نام RenderAsynchronous به سرور ارسال می‌شود. چون عملیات Ajax غیرهمزمان است، کاربر نیازی نیست تا رندر شدن کامل صفحه ابتدا صبر کند و سپس کل صفحه به او نمایش داده شود. در اینجا ابتدا صفحه به صورت کامل نمایان شده و سپس درخواستی Ajax ایی به سرور ارسال می‌گردد. در پایان عملیات، Partial View یاد شده رندر گردیده و در div ایی با id مساوی info نمایش داده می‌شود.
به این ترتیب می‌توان حس سریع بودن سایت را زمانیکه قسمتی از صفحه نیاز به زمان بیشتری برای نمایش اطلاعات دارد، به کاربر منتقل کرد.

دریافت پروژه کامل این قسمت
jQueryMvcSample07.zip 
نظرات مطالب
Highlight کردن لینک صفحه جاری در ASP.NET MVC
بله. هدف این بوده تا حد ممکن شخص بدونه در چه قسمتی از سایت است. مثلا اگر به جزئیات یک پروژه مراجعه کرده، باید بدونه در قسمت پروژه‌ها هست.
این کد جدید و اصلاح شده است:
if (typeof String.prototype.startsWith != 'function') {
    // see below for better implementation!
    String.prototype.startsWith = function (str) {
        return this.indexOf(str) == 0;
    };
}

$(document).ready(function () {
    $("#headermenu a").each(function () {
        var $a = $(this);
        var href = $a.attr("href");

        var path = $.trim(location.pathname.toLowerCase());
        var menuHref = $.trim(href.toLowerCase());

        if (href && (path == menuHref || (menuHref != '/' && path.startsWith(menuHref)))) {
            //صفحه جاری را یافتیم
            $a.css({
                "color": "Yellow",
                "border-bottom": "2px solid"
            });
        }
    });
});