Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Pjax: pushState and Ajax Using ASP.NET Webforms

0.00/5 (No votes)
27 Feb 2016 2  
Pjax ASP.NET webform is simple code that uses Ajax and pushState to deliver a fast browsing experience with real permalinks, page titles, and a working back button.

Introduction

Pjax ASP.NET webforms is working by loading *.aspx pages from your server via Ajax and replacing the content of a page working area <div class="working-area"><!-- *.aspx page`s content comes here using ajax--></div> on your page with the Ajax result html.

And also updates the browser's current URL using pushState without reloading your page's layout or any resources (JS, CSS), giving the appearance of a fast, full page load. But really it's just Ajax and pushState.

You can easily reload your page (refresh it) any time and save website history to apply forward and back form your browser with target url page content.

Pjax ASP.NET webforms demo can be found here.

Background

You can see the main project of Pjax https://github.com/defunkt/jquery-pjax and its demo: http://pjax.herokuapp.com/aliens.html that may apply on *.html page and ASP.NET MVC itself not working with ASP.NET webforms.

Using the Code

In our code, we will use:

  1. Two masterpages (one of them for normal loading and the other for partial (Ajax) load).
  2. JavaScript code file (to handle website links navigation and partial loading with updating website url).
  3. C# base class for every inherited page (help us to differentiate between normal and clicked loading every *.aspx page)

We will keep all scripts and styles in the default masterpage Site.master contains all elements for normal loading as the following code:

<%@ Master Language="C#" AutoEventWireup="true" 
    CodeFile="Site.master.cs" Inherits="SiteMaster" %>

<meta charset="utf-8" /><meta name="viewport" 
content="width=device-width, initial-scale=1.0" />
<title></title>
<asp:placeholder runat="server"> 
<%: Scripts.Render("~/bundles/modernizr") %> </asp:placeholder>

<p><webopt:bundlereference path="~/Content/css" runat="server">
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" /> <%--
<base href="/" id="appUrl" />--%> </webopt:bundlereference></p>

<form runat="server"><asp:scriptmanager runat="server"> <Scripts>
                <%--To learn more about bundling scripts in ScriptManager 
                see http://go.microsoft.com/fwlink/?LinkID=301884 --%>
                <%--Framework Scripts--%>
                <asp:ScriptReference Name="MsAjaxBundle" />
                <asp:ScriptReference Name="jquery" />
                <asp:ScriptReference Name="bootstrap" />
                <asp:ScriptReference Name="respond" />
                <asp:ScriptReference Name="WebForms.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/WebForms.js" />
                <asp:ScriptReference Name="WebUIValidation.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/WebUIValidation.js" />
                <asp:ScriptReference Name="MenuStandards.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/MenuStandards.js" />
                <asp:ScriptReference Name="GridView.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/GridView.js" />
                <asp:ScriptReference Name="DetailsView.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/DetailsView.js" />
                <asp:ScriptReference Name="TreeView.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/TreeView.js" />
                <asp:ScriptReference Name="WebParts.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/WebParts.js" />
                <asp:ScriptReference Name="Focus.js" 
                Assembly="System.Web" Path="~/Scripts/WebForms/Focus.js" />
                <asp:ScriptReference Name="WebFormsBundle" />
                <%--Site Scripts--%>
            </Scripts>
        </asp:ScriptManager>

        <div class="navbar navbar-inverse navbar-fixed-top">
            <div class="container">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" 
                    data-toggle="collapse" data-target=".navbar-collapse">
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </button>
                    <a class="navbar-brand" runat="server" href="~/">Application name</a>
                </div>
                <div class="navbar-collapse collapse">
                    <ul class="nav navbar-nav">
                        <li><a runat="server" href="/">Home</a></li>
                        <li><a runat="server" href="/About">About</a></li>
                        <li><a runat="server" href="/Contact">Contact</a></li>
                    </ul>
                    <asp:LoginView runat="server" ViewStateMode="Disabled">
                        <AnonymousTemplate>
                            <ul class="nav navbar-nav navbar-right">
                                <li><a class="normal" runat="server" 
                                href="/Account/Register">Register</a></li>
                                <li><a class="normal" runat="server" 
                                href="/Account/Login">Log in</a></li>
                            </ul>
                        </AnonymousTemplate>
                        <LoggedInTemplate>
                            <ul class="nav navbar-nav navbar-right">
                                <li><a runat="server" class="normal" 
                                href="/Account/Manage" title="Manage your account">Hello, 
                                <%: Context.User.Identity.GetUserName()  %>!</a></li>
                                <li>
                                    <asp:LoginStatus runat="server" class="normal" 
                                    LogoutAction="Redirect" LogoutText="Log off" 
                                    LogoutPageUrl="~/" OnLoggingOut="Unnamed_LoggingOut" />
                                </li>
                            </ul>
                        </LoggedInTemplate>
                    </asp:LoginView>
                </div>
            </div>
        </div>
        <div class="container body-content" id="body-content">
            <asp:ContentPlaceHolder ID="MainContent" runat="server">
            </asp:ContentPlaceHolder>
        </div>
        <div class="container body-content">
            <hr />
            <footer>
                <p>&copy; <%: DateTime.Now.Year %> - My ASP.NET Application, 
                <span class="text-danger" title="Time did not change"><strong>Time: 
                </strong> <%: DateTime.Now.TimeOfDay %></span></p>
            </footer>
        </div>
    </form>

    <script src="/Scripts/handlePages.min.js"></script></form>

Then we add a new empty master page called Site2.master as the following code:

<%@ Master Language="C#" AutoEventWireup="true" 
CodeFile="Site2.master.cs" Inherits="Site2" %>

<head runat="server"> </head>
<asp:contentplaceholder id="MainContent" 
runat="server"></asp:contentplaceholder>

The next step: we will create FeachMasterPage.cs C# base class inside App_Code for website pages to sense type of page's loading by user click or normal loading, it describes as the following:

using System; 
public abstract class FeachMasterPage : System.Web.UI.Page 
{     
    protected void Page_PreInit(object sender, EventArgs e)     
    {         
       if (!string.IsNullOrEmpty(Request.QueryString["click"]))             
           this.MasterPageFile = "/Site2.master";     
    } 
}

we use a Page_PreInit event that can fire before site master page and helps us to determine the masterpage based on way of page load (normally or by user click) see asp.net page lifecycle.

The next: Inside every page, we can inherit from our base class: FeachMasterPage as the following:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class About : FeachMasterPage //Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
       // page code goes here..
    }
}

The last step: creating Js file inside Scripts folder called handlePages.js to handle user click as the following:

//#region pages handler
(function () {
    "use strict";
    // featch page contents (html, js, css,...)
    function loadPage(widgetUri) {
        var containerObjLocal = $('#body-content'), $progress = $('#loading');
        $progress.css('display', 'block');
        containerObjLocal.html('<span class="center">
        <i class="icon-spinner center icon-spin orange bigger-125"></i></span>');
        $.get(widgetUri, function (response) {
            console.log(response)
            containerObjLocal.html(response).fadeIn('fast');
        }).done(function () {
            // init page plugins
        }).fail(function () {
            //error alert
            alert('Please reload the page by pressing f5');
            //window.location.href = path;
        }).always(function () {
            $('html,body').animate({ scrollTop: 0 }, 'slow');
            // hide progress
            $progress.css('display', 'none');
        });
    }
    // change url without reloading page
    function ChangeUrl(page, url) {
        if (history.pushState) {
            var oj = { Page: page, Url: url };
            history.pushState(oj, oj.Page, oj.Url);
        } else {
            console.log("Browser does not support HTML5.");
        }
    }
   // open page content and change url
    function openPage(title, path) {
        try {
            ChangeUrl(title, path); // assign page url
            var separator = path.indexOf('?') > -1 ? 
            '&' : '?', url = path + separator + 'click=yes';
            // start load page contents
            loadPage(url);
        }
        catch (ex) {
            alert(ex);
        }
    }

    // pages anchor click event
    $(document).delegate('a[href]:not([href=#])', 'click', function (e) {
        var path = $(this).attr('href');
        // add content only flag
        // filter link before call page contents
        var isClick = true;
        $.map(['www.', 'http', 'javascript', 
        '@', 'mailto', 'default.aspx', '#'], function (v, i) {
            if (path.indexOf(v) > -1) {
                isClick = false;
                return;
            }
        });
        // prevent partial loading in these cases: 
        // the link has class="normal" or has target attributes.
        if (isClick && !$(this).attr('target') 
        && !$(this).hasClass('normal')) {
            e.preventDefault();
            openPage($(this).text(), path);
        }
    });

   // sense page history back and forward
    window.onpopstate = function (e) {
        // open this page
        var obj = e.state;
        if (obj && obj.Url) {
            openPage(obj.Page, obj.Url);
        }
    }

})();
//#endregion

As shown in the latest JS snippet code, we find openPage function that calls another changeUrl function (for changing current url without reloading our webpage) and loadPage function that helps in gathering page contents like html, Js, CSS,... and shows it inside <div id="body-content"></div> in main master page Site.master using ajax $.get() method.

Points of Interest

  • Normal load or at page refresh (F5) or opening my page in a new tab, in this case our page will load normally throw default Site.master masterpage.
  • When user clicks on any navigation link (as website menu), Js code will sense this event and then change current page url and render new target page contents inside <div id="body-content"></div> without page reload.

Pjax ASP.NET webforms demo: http://pjax.share-web-design.com/

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here