استفاده از pjax بجای ajax در ASP.NET MVC
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: پنج دقیقه

عموما از ajax برای ارائه سایت‌هایی سریع، با حداقل ریفرش و حداقل مصرف پهنای باند سرور، استفاده می‌شود. اما این روش، مشکلات خاص خود را نیز دارا است. عموما محتوای پویای بارگذاری شده، سبب تغییر آدرس صفحه‌ی جاری در مرورگر نمی‌شود. برای مثال اگر قرار است چندین برگه در صفحه به صورت ajax ایی بارگذاری شوند، تغییر سریع محتوا را مشاهده می‌کنید، اما خبری از تغییر آدرس جاری صفحه در مرورگر نیست. همچنین روش‌های ajax ایی عموما SEO friendly نیستند. زیرا اکثر موتورهای جستجو فاقد پردازشگرهای جاوا اسکریپت می‌باشند و محتوای پویای ajax ایی را مشاهده نمی‌کنند. برای آدرس دهی این مشکلات مهم، افزونه‌ای به نام pjax طراحی شده‌است که کار آن دریافت محتوای HTML ایی از سرور و قرار دادن آن در یک جایگاه خاص مانند یک div است. در پشت صحنه‌ی آن از jQuery ajax استفاده شده، به همراه push state

pjax = pushState + AJAX
Push state API همان HTML5 History API است؛ به این معنا که هرچند محتوای صفحه‌ی جاری به صورت پویا بارگذاری می‌شود، اما آدرس مرورگر نیز به صورت خودکار تنظیم خواهد شد؛ به همراه عنوان صفحه. به علاوه تاریخچه‌ی مرور صفحات نیز در مرورگر به روز رسانی شده و امکان حرکت بین صفحات توسط دکمه‌های back و forward همانند قبل وجود خواهد داشت. همچنین اگر مرورگر جاری سایت، امکان استفاده از جاوا اسکریپت را نداشته باشد، به صورت خودکار به حالت بارگذاری کامل صفحه سوئیچ خواهد کرد.
سایت‌های بسیاری خودشان را با این الگو وفق داده‌اند. برای نمونه Twitter و Github از مفهوم pjax استفاده‌ی وسیعی دارند. برای نمونه، layout یا master page یک سایت را درنظر بگیرید. به ازای مرور هر صفحه، یکبار باید تمام قسمت‌های تکراری layout از سرور بارگذاری شوند. توسط pjax به سرور اعلام می‌کنیم، ما تنها نیاز به body صفحات را داریم و نه کل صفحه را. همچنین اگر مرورگر از جاوا اسکریپت استفاده نمی‌کند، لطفا کل صفحه را همانند گذشته بازگشت بده. به علاوه مسایل سمت کلاینت مانند تغییر آدرس مرورگر و تغییر عنوان صفحه نیز به صورت خودکار مدیریت شوند. این تکنیک را دقیقا در حین مرور مخزن‌های کد Github می‌توانید مشاهده کنید. فقط قسمتی که لیست فایل‌ها را ارائه می‌دهد، از سرور دریافت می‌گردد و نه کل صفحه.


بکارگیری pjax در ASP.NET MVC

مطابق توضیحاتی که ارائه شد، برای پیاده سازی سازی pjax نیاز به دو فایل layout داریم. یکی برای حالت ajax ایی و دیگری برای حالت بارگذاری کامل صفحه. حالت ajax ایی آن تنها از رندرکردن body پشتیبانی می‌کند؛ و نه ارائه تمام قسمت‌های صفحه مانند هدر، فوتر، منوها و غیره. بنابراین خواهیم داشت:

الف) تعریف فایل‌های layout سازگار با pjax
ابتدا یک فایل جدید را به نام _PjaxLayout.cshtml به پوشه‌ی Shared اضافه کنید؛ با این محتوا:
 <title>@ViewBag.Title</title>
@RenderBody()
سپس layout اصلی سایت را به نحو ذیل تغییر دهید
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="~/Content/Site.css" rel="stylesheet" />
    <script src="~/Scripts/jquery-1.8.2.min.js"></script>
    <script src="~/Scripts/jquery.pjax.js"></script>

    <script type="text/javascript">
        $(function () {
            $(document).pjax('a[withpjax]', '#pjaxContainer', { timeout: 5000 });
        });
    </script>
</head>
    <body>
        <div>Main layout ...</div>
        <div id="pjaxContainer">
            @RenderBody()
        </div>
    </body>
</html>
در فایل PjaxLayout خبری از هدر و فوتر نیست و فقط یک عنوان و نمایش body را به همراه دارد.
فایل layout اصلی سایت همانند قبل است. فقط RenderBody آن داخل یک div با id مساوی pjaxContainer قرار گرفته و از آن در فراخوانی افزونه‌ی pjax استفاده شده‌است. همانطور که ملاحظه می‌کنید، مطابق تنظیمات ابتدای هدر layout، فقط لینک‌هایی که دارای ویژگی withpjax باشند، توسط pjax پردازش خواهند شد.

ب) تغییر فایل ViewStart برنامه
در فایل ViewStart، کار مقدار دهی layout پیش فرض صورت گرفته‌است. اکنون نیاز است این فایل را جهت معرفی layout دوم تعریف شده مخصوص pjax، اندکی ویرایش کنیم:
@{
    if (Request.Headers["X-PJAX"] != null)
    {
        Layout = "~/Views/Shared/_PjaxLayout.cshtml";
    }
    else
    {
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
}
افزونه‌ی pjax، هدری را به نام X-PJAX به سرور ارسال می‌کند. بر این اساس می‌توان تصمیم گرفت که آیا از layout اصلی (در صورتیکه مرورگر از جاوا اسکریپت پشتیبانی نمی‌کند و این هدر را ارسال نکرده‌است) یا از layout سبک‌تر pjax استفاده شود.

ج) آزمایش برنامه
using System.Web.Mvc;

namespace PajxMvcApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            return View();
        }
    }
}
یک کنترلر ساده را به نحو فوق با دو اکشن متد و دو View متناظر با آن ایجاد کنید.
سپس View متد Index را به نحو ذیل تغییر دهید:
 @{
ViewBag.Title = "Index";
}

<h2>Index</h2>

@Html.ActionLink(linkText: "About", actionName:"About", routeValues: null,
                         controllerName:"Home", htmlAttributes: new { withpjax = "with-pjax"})
در این View یک لینک معمولی به اکشن متد About اضافه شده‌است. فقط در ویژگی‌های html آن، یک ویژگی جدید به نام withpjax را نیز اضافه کرده‌ایم تا در صورت امکان و پشتیبانی مرورگر، از pjax استفاده شود.
اکنون اگر برنامه را اجرا کنید، چنین خروجی را در برگه‌ی network آن مشاهده خواهید کرد:



همانطور که ملاحظه می‌کنید، با کلیک بر روی لینک About، یک درخواست pjax ایی به سرور ارسال شده‌است؛ به همراه هدرهای ویژه آن. هنوز قسمت‌های اصلی layout سایت مشخص هستند (و مجددا از سرور درخواست نشده‌اند). آدرس صفحه عوض شده‌است. به علاوه قسمت body آن تنها تغییر کرده‌است.



این مثال را از اینجا نیز می‌توانید دریافت کنید
PajxMvcApp.zip


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

A Faster Web With PJAX
Favour PJAX over dynamically loaded partial views
What is PJAX and why
Pjax.Mvc
Using pjax with ASP.Net MVC3
Getting started with PJAX with ASP.NET MVC
ASP.NET MVC with PAjax or PushState/ReplaceState and Ajax
  • #
    ‫۱۰ سال و ۴ ماه قبل، شنبه ۳ خرداد ۱۳۹۳، ساعت ۲۰:۵۳
    با سلام؛ آیا pjax توسط کلیه مرورگرها پشتیبانی میشه؟ مخصوصا IE ؟
    • #
      ‫۱۰ سال و ۴ ماه قبل، شنبه ۳ خرداد ۱۳۹۳، ساعت ۲۱:۳۱
      - فقط در مرورگرهایی پشتیبانی می‌شود که push state را پیاده سازی کرده باشند: لیست کامل آن‌ها
      - اگر مرورگری history.pushState API را پشتیبانی نکند، بارگذاری صفحات آن معمولی خواهند بود (شبیه به حالت بارگذاری کامل برای موتورهای جستجو؛ بدون از کار افتادن برنامه).
  • #
    ‫۱۰ سال و ۴ ماه قبل، دوشنبه ۵ خرداد ۱۳۹۳، ساعت ۰۴:۲۶
    با سلام
    آیا در Asp Web Form هم قابل استفاده است؟
    • #
      ‫۱۰ سال و ۴ ماه قبل، دوشنبه ۵ خرداد ۱۳۹۳، ساعت ۰۴:۵۶
      در مثالی که توضیح داده شد، محتوای قسمتی از صفحه به صورت پویا با محتوای صفحه‌ای دیگر جایگزین می‌شود.
      صفحه‌ی اول با master page کامل سایت رندر می‌شود. با کلیک بر روی لینک مشاهده‌ی صفحه‌ی بعدی، فقط محتوای آن صفحه (بدون master page اصلی سایت؛ شکل دوم) بجای div محتوای صفحه‌ی اول تزریق می‌شود.
      اگر صفحه‌ی دوم به صورت معمولی درخواست شود، با master page کامل سایت رندر خواهد شد.
      اما ... در وب فرم‌ها هر چند امکان انتخاب master page به صورت پویا وجود دارد، اما به علت اینکه هر صفحه View State خاص خودش را خواهد داشت (بر اساس کنترل‌هایی که دارد)، تزریق محتوای آن داخل یک صفحه‌ی دیگر سبب تخریب View state جاری و از پیش موجود می‌شود. در نتیجه امکان ارسال اطلاعات به سرور را با پیام view state is corrupted از دست خواهید داد.
  • #
    ‫۱۰ سال و ۴ ماه قبل، دوشنبه ۵ خرداد ۱۳۹۳، ساعت ۱۷:۳۵
    با سلام؛ تمام مراحل رو انجام دادم اما جواب نمیده. مشکل از کد زیر که نیست؟

     @Html.ActionLink(item.Name, item.ActionName, item.ControllerName,
     new { Id = item.Id, area = item.AreaName }, new { @class = "drop", withpjax = "with-pjax" })
  • #
    ‫۱۰ سال و ۴ ماه قبل، دوشنبه ۵ خرداد ۱۳۹۳، ساعت ۱۹:۱۳
    با سلام؛ زمانی که یک ویو رو از طریق pjax نمایش میدم اگه داخل اون ویو یک لینک وجود داشته باشد که از طریق متد post یک اکشن رو فراخوانی می‌کند لینک کار نمی‌کند. مثلا در پروژه IRIS توی بعضی صفحات که دکمه امتیاز دهی وجود داره اگه این صفحات با pjax لود بشن دیگه دکمه‌ها کار نمی‌کنن. به نظر شما مشکل کجاست؟ آیا اسکریپت‌ها لود نمیشن؟
    • #
      ‫۱۰ سال و ۴ ماه قبل، دوشنبه ۵ خرداد ۱۳۹۳، ساعت ۱۹:۳۱
      - المان‌هایی که به صورت پویا به صفحه اضافه می‌شوند، تحت کنترل مجدد jQuery نخواهند بود، مگر اینکه از متدهای live (منسوخ شده) و یا on استفاده شود. مطابق مستندات این کتابخانه (انتهای صفحه)، متد on به صورت پیش فرض، برای کارکرد مجدد pjax، اعمال می‌شود. بنابراین فقط باید بررسی کنید که آیا نگارش jQuery مورد استفاده، از متد on پشتیبانی می‌کند یا خیر (از آخرین نگارش آن استفاده کنید).
      - همچنین تمام کدهای سایر قسمت‌های برنامه هم مانند دکمه امتیازدهی که اشاره شد، باید تغییر کرده و از متد on استفاده کنند. مثلا اگر click دارند، باید بشوند on click.
      //The new api
      $(document).pjax('a[withpjax]', '#pjax-container')
      
      //Which is roughly the same as
      $(document).on('click', 'a[withpjax]', function(event) {
        $.pjax.click(event, '#pjaxContainer')
      })
  • #
    ‫۱۰ سال و ۴ ماه قبل، شنبه ۱۷ خرداد ۱۳۹۳، ساعت ۰۱:۴۲
    همه چیز خوبه؛ اما وقتی توی گرید میاییم میزنیم Edit و میریم صفحه Edit و گزینه Save رو میزنیم، برمیگردیم به صفحه Index در حالی که لینک تغییر نکرده و لینک مثلاً اینطوری مونده
    www.test.com/edit/1
    و وقتی میزنیم گرید به صفحه 2 بره ارور میده که اصلاً همچین صفحه ای وجود نداره
    حالا راهی نیست که از Controller  که کد زدیم برای Edit و در آخر گفتیم بره به صفحه Index این Pjax و اینا رو آخر لینک ننویسه؟

    • #
      ‫۱۰ سال و ۴ ماه قبل، شنبه ۱۷ خرداد ۱۳۹۳، ساعت ۰۱:۵۵
      در مثالی که زده شد، فقط لینک‌هایی که دارای ویژگی withpjax هستند تحت کنترل این افزونه قرار می‌گیرند و نه هیچ لینک دیگری در برنامه و نه هیچ روش بازگشت دیگری:
        $(document).pjax('a[withpjax]', '#pjaxContainer', { timeout: 5000 });
      البته این یک مثال است و اگر مثلا withpjax آن‌را حذف کنید:
      $(document).pjax('a', '#pjaxContainer', { timeout: 5000 });
      تمام لینک‌های صفحه تحت کنترل خواهند بود. شبیه به حالتی که عنوان کردید صفحه بندی گرید به هم خورده. برای اینکه این نوع تداخل‌ها رخ ندهند و هر لینکی در صفحه توسط این افزونه پردازش نشود، بهتر است از روش پیشنهادی استفاده کنید.
      • #
        ‫۱۰ سال و ۴ ماه قبل، شنبه ۱۷ خرداد ۱۳۹۳، ساعت ۰۲:۱۳
        ممنون از جوابتون اما مشکل من همینجاست که اصلاً به هیچ خصوصیتی ندادم
          <input type="submit" value="Save" />
        اینو گذاشتم اما چون توی
         @Ajax.beginform(...)
        هست و توی
         <div id="pjaxContainer">
          @RenderBody()
        </div>
        که توی layout  هست داره بصورت pjax عمل میکنه.
        • #
          ‫۱۰ سال و ۴ ماه قبل، شنبه ۱۷ خرداد ۱۳۹۳، ساعت ۰۲:۳۲
          - لازم هست با نحوه‌ی دیباگ برنامه‌های Ajax و برنامه‌های مبتنی بر jQuery آشنا شوید (جهت بررسی اینکه مشخص شود بدون تنظیمات این افزونه، آیا عملیات انجام شده، Ajax ایی است یا Pjax ایی). اطلاعات بیشتر
          - کار pjax فقط ارائه محتوای صفحات است. اگر فعال هم نباشد، برنامه بدون مشکل کار می‌کند و صفحات آن نمایش داده خواهند شد.
        • #
          ‫۱۰ سال و ۴ ماه قبل، یکشنبه ۱۸ خرداد ۱۳۹۳، ساعت ۱۶:۰۹
          تصور بنده بر اینه که شما باید برای داشتن فرم همون Html.BeginForm معمولی رو استفاده کنید و نه Ajax.BeginForm چون به صورت توکار pjax داره از Ajax بهره می‌بره ممکنه یه تداخلی هم این وسط پیش بیاد
          • #
            ‫۱۰ سال و ۴ ماه قبل، دوشنبه ۱۹ خرداد ۱۳۹۳، ساعت ۰۵:۲۳
            مشکل همینجاست فکر کنم که از beginForm چه عادی چه Ajax ای استفاده میشه
            در حالت Ajax که مشکل اساسی هست و چندتا چندتا INsert میشه و یه جورایی همینطور تو حلقه میمونه
            از Html.beginForm استفاده میکنم رفرش میشه
            از هیچکدوم استفاده نکنم نمیدونم چطوری اطلاعات صفحه رو به یه Action  توی کنترلر با دکمه Submit ارسال کنم!
  • #
    ‫۱۰ سال و ۴ ماه قبل، شنبه ۱۷ خرداد ۱۳۹۳، ساعت ۰۲:۰۸
    نمی دونم Pjax این مشکلا رو چطوری بوجود میاره، اما از وقتی ازش استفاده کردم، توی Save کردن در صورتی که از گزینه‌های خود یک Textbox که خود Browser یشنهاد میده استفاده بشه، چندتا چندتا ذخیره میشه:


    و اینم خروجی یک Save 

  • #
    ‫۹ سال و ۱۲ ماه قبل، شنبه ۱۹ مهر ۱۳۹۳، ساعت ۲۰:۴۸
    با سلام
    در ajax.actionlink میشد مثلا یه loading نمایش داد خواستم ببینم در pjax هم این امکان وجود داره؟

    با تشکر
    • #
      ‫۹ سال و ۱۲ ماه قبل، شنبه ۱۹ مهر ۱۳۹۳، ساعت ۲۱:۱۱
      به مستندات آن مراجعه کنید (^). قسمت رویدادهای آن یک مثال loading دارد:
      $(document).on('pjax:send', function() {
        $('#loading').show()
      })
      $(document).on('pjax:complete', function() {
        $('#loading').hide()
      })
  • #
    ‫۹ سال و ۱۱ ماه قبل، یکشنبه ۲۰ مهر ۱۳۹۳، ساعت ۱۵:۵۰
    با سلام؛ وقتی که از Pjax در برنامه استفاده می‌شود و مثلا فرم لاگین را با آن اجرا می‌کنیم اگر از دستور
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }
    در ویو استفاده کنیم دوبار به اکشن login می‌رود که در دفعه اول به صورت Pjax برای ما فرم را می‌آورد ولی در دفعه دوم به صورت معمولی
    در مثال زیر این مورد مفصلا مطرح شده
    دانلود مثال
    خواستم ببینم که مشکل از چی می‌تونه باشه؟
  • #
    ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۵ آبان ۱۳۹۳، ساعت ۱۴:۱۰
    با سلام
    زمانی که از pjax استفاده می‌کنیم اگر قرار باشه ویویی رو که دارای یک فرم برای ارسال اطلاعات به سرور است را نمایش دهیم با خطای زیر رو به رو می‌شویم.

    و بعد از نمایش خطا کل صفحه رفرش می‌شود و مثل اینه که اصلا pjax کار نداده.
    همه‌ی مراحل را هم در مثال قبل که ارسال شد انجام داده ام ولی باز به اینصورت جواب میده.
    • #
      ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۵ آبان ۱۳۹۳، ساعت ۱۴:۱۵
      صفحه‌ی کامل internal error در همینجا قابل مشاهده است؛ به همراه ریز خطای آن. روی + آن کلیک کنید و بعد محتوای برگه‌ی response را جهت یافتن متن استثنای حاصل بررسی کنید.
      • #
        ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۵ آبان ۱۳۹۳، ساعت ۱۴:۳۶
        این خطا برای یه لحظه به من نمایش داده می‌شود و بعد کل خطاها پاک می‌شود و از نو صفحه بارگذاری می‌شود.
        راهی هست که در زمان نمایش خطا به این صورت که سریع پاک می‌شود به اطلاعات آن دسترسی داشت؟

        و اینکه اصلا این عمل صحیح است که جاهایی که قراره اطلاعات رو از کاربر دریافت کنیم از Pjax  استفاده شود؟
        • #
          ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۵ آبان ۱۳۹۳، ساعت ۱۴:۵۵
          internal server error صرفا به معنای بروز استثنایی در کدهای سمت سرور شما است. برای لاگ کردن دقیق ریز جزئیات آن‌ها از ELAMH استفاده کنید. نسخه‌ی ساده شده‌ی آن برای ASP.NET MVC در اینجا
          • #
            ‫۹ سال و ۱۱ ماه قبل، سه‌شنبه ۶ آبان ۱۳۹۳، ساعت ۱۵:۳۱
            با سلام
            از کمک شما ممنون
            بالاخره خطا رو پیدا کردم
            The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/_PjaxLayout.cshtml": "Scripts".
            ولی دلیلش چی می‌تونه باشه مگه فقط نمیاد قسمت مثلا main در کد زیر را جایگذاری کنه؟
            <div id="main">
                        @RenderBody()
                    </div>
            //********** @Scripts.Render("~/bundles/jquery")
            @Scripts.Render("~/bundles/bootstrap")
            @RenderSection("Scripts", required: false)
            و برای فراخوانی لینک‌های pjax نوشته شده:
            <script type="text/javascript">
                    $(function () {
                        $(document).pjax('a[withpjax]', '#main', { timeout: 5000 });

            و لینک هم به اینصورت:
            @Html.ActionLink("ارتباط با ما","Contact", "Home"
                                                  , null,new { withpjax="with-pjax" })

            • #
              ‫۹ سال و ۱۱ ماه قبل، سه‌شنبه ۶ آبان ۱۳۹۳، ساعت ۱۵:۵۹
              یعنی فایل _PjaxLayout.cshtml هم نیاز به یک سری تعاریف section را دارد؛ مانند:
              @RenderSection("Scripts", false)
              کلا View ایی که قرار است رندر شود، اگر دارای تعاریف section اختصاصی هست، باید معادل آن‌ها در فایل layout متناظر، تعریف RenderSection وجود داشته باشد.
  • #
    ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۱۹ آبان ۱۳۹۳، ساعت ۱۵:۴۳
    با سلام.
    من وقتی از Pjax در یک View که هیچ Layout یی ندارد استفاده می‌کنم به خوبی جواب می‌دهد و صفحه رفرش نمی‌شود .(قسمت پیج بندی یک لیست از اطلاعات)
    اما وقتی View  را در یک Layout قرار می‌دهم صفحه رفرش می‌شود. جای اسکریپت‌ها را در view  و Layout تغییر دادم هیچ تاثیری نداشت . مشکل کار من کجاست ؟
    • #
      ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۱۹ آبان ۱۳۹۳، ساعت ۱۵:۵۳
      مراجعه کنید به توضیحات قسمت «ب) تغییر فایل ViewStart برنامه ». اگر Layout یک View ذکر نشود، اطلاعات آن‌را از ViewStart دریافت می‌کند. اگر آن‌را صریحا ذکر کنید، همان کاری که در ViewStart برای تشخیص هدر ["Request.Headers["X-PJAX انجام شده، در این حالت باید به صورت دستی انجام و اضافه شود.
      • #
        ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۱۹ آبان ۱۳۹۳، ساعت ۱۶:۰۵
        من Layout را در View به این شکل مقدار دهی می‌کنم : Layout="" . که در این حالت pjax به خوبی جواب می‌دهد. البته من اون دو خط اسکریپت فایل‌های js و اون تکه کد را در view  قرار دادم. من فقط از pjax برای پیج بندی لیست اطلاعاتم فقط در این view میخوام استفاده کنم.
        اما من میخوام وقتی view خود را در Layout میگذارم جواب دهد.
        لیست محصولات من در یک Partial قرار دارد که این Partial هم در View هست .
        • #
          ‫۹ سال و ۱۱ ماه قبل، دوشنبه ۱۹ آبان ۱۳۹۳، ساعت ۱۷:۲۴
          برای دیباگ کار، بررسی کنید:
          - آیا لینک‌هایی که بر روی آن‌ها کلیک می‌شود، ویژگی withpjax را دارند؟ آیا اسکریپت متناظر با آن به صفحه پیوست شده؟ 
          آیا خطایی مشاهده نمی‌شود؟
          - آیا header مربوط به X-PJAX در درخواست ارسالی به سرور مطابق تصاویر فوق وجود دارند؟
          - آیا در سمت سرور بر اساس هدر
          X-PJAX دریافتی، فایل layout صحیحی تنظیم می‌شود؟