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:
- Two masterpages (one of them for normal loading and the other for partial (Ajax) load).
- JavaScript code file (to handle website links navigation and partial loading with updating website url).
- 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: <%--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>© <%: 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 {
protected void Page_Load(object sender, EventArgs e)
{
}
}
The last step: creating Js file inside Scripts folder called handlePages.js to handle user click as the following:
(function () {
"use strict";
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 () {
}).fail(function () {
alert('Please reload the page by pressing f5');
}).always(function () {
$('html,body').animate({ scrollTop: 0 }, 'slow');
$progress.css('display', 'none');
});
}
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.");
}
}
function openPage(title, path) {
try {
ChangeUrl(title, path); var separator = path.indexOf('?') > -1 ?
'&' : '?', url = path + separator + 'click=yes';
loadPage(url);
}
catch (ex) {
alert(ex);
}
}
$(document).delegate('a[href]:not([href=#])', 'click', function (e) {
var path = $(this).attr('href');
var isClick = true;
$.map(['www.', 'http', 'javascript',
'@', 'mailto', 'default.aspx', '#'], function (v, i) {
if (path.indexOf(v) > -1) {
isClick = false;
return;
}
});
if (isClick && !$(this).attr('target')
&& !$(this).hasClass('normal')) {
e.preventDefault();
openPage($(this).text(), path);
}
});
window.onpopstate = function (e) {
var obj = e.state;
if (obj && obj.Url) {
openPage(obj.Page, obj.Url);
}
}
})();
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/