DNN Session Timer with AngularJS
This tutorial will guide you through the steps necessary to add a session timeout timer and alert dialog within a DotNetNuke (DNN) Module using AngularJS. Key concepts:
- Using DNN server objects to create AngularJS client-side objects;
- Using DNN jQuery and jQuery UI registration;
- Using DNN Client Resource Management;
- Using DNN Form Patterns
Requirements: DotNetNuke 7; AngularJS; An understanding of how to develop and install DotNetNuke modules. There are a number of resources dedicated to this topic, including my DotNetNuke 7 Module Templates blog post earlier this year.
DotNetNuke Page Setup
The Session Timer is designed to alert the user that they have been inactive for X number of minutes and their session is about to expire. They are presented with a dialog based on a threshold setting defined in a custom module setting to extend the session. Once the session has expired, the dialog content changes to notify the user of their expired session status. Further action will redirect the user to the login page to re-establish a new session. For this to work, your module will need to be placed on a page that is visible only to logged in users. You can check or modify the page permissions while logged in as admin or host at Admin > Page Management.
jQuery UI Registration
Our session alert will be using a jQuery UI modal dialog. Therefore we need to make sure that the jQuery and jQuery UI resources are loaded. jQuery and jQuery-UI are included with DNN. When registering jQuery-UI, it will register the dependent jQuery library as well. In you Module Control code behind, register the jQuery UI resources in the View module control code behind OnLoad or OnInit method:
View.ascx.cs
protected override void OnLoad(EventArgs e)
{
DotNetNuke.Framework.jQuery.RequestUIRegistration();
}
AngularJS
Download AngularJS and place both angular.min.js
and angular.js
in a new folder named Library/Angular
within your Module folder. For example, I have named my Module “Angular” so here is what the structure looks like in my Visual Studio Solution Explorer:
Suggested location of AngularJS library files within a DNN Module
Create a file for our AngularJS JavaScript. For Visual Studio users, in Solution Explorer, right click on the Angular project, select Add > New Folder, name it “Scripts”. Now right click on the Scripts folder you just added and select Add > JavaScript file and name it SessionTimer.js
. For now, add a single line at the top of this file to enable strict mode:
SessionTimer.js
'use strict';
We will use the DNN Client Resource Management API to load our angular.js
and SessionTimer.js
files. To enable this API in our module, we need to reference two assemblies in the root bin of our DNN website. Therefore, right click on the References folder and browse to the following dll files and reference them:
ClientDependency.Core.dll
DotNetNuke.Web.Client.dll
Add the following markup to the View module control to load the JavaScript resources. As a best practice, we are using the ForceProvider attribute with a value of DnnFormBottomProvider in our DnnJsInclude server controls to load these JavaScript resources at the bottom of the page. Additionally, we are going to use a Priority attribute to ensure they are loaded in the order listed.
View.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="View.ascx.cs" Inherits="DotNetNuke.Modules.Angular.View" %>
<%@ Register TagPrefix="dnn" Namespace="DotNetNuke.Web.Client.ClientResourceManagement" Assembly="DotNetNuke.Web.Client" %>
<dnn:DnnJsInclude ID="DnnAngularSessionInclude" runat="server" FilePath="~/DesktopModules/Angular/Scripts/SessionTimer.js" ForceProvider="DnnFormBottomProvider" Priority="200" />
<!-- In production, use minified version (angular.min.js) -->
<dnn:DnnJsInclude ID="DnnAngularInclude" runat="server" FilePath="~/DesktopModules/Angular/Library/Angular/angular.js" ForceProvider="DnnFormBottomProvider" Priority="210" />
Now add another line to the SessionTimer.js to define our AngularJS module:
SessionTimer.js
'use strict';
var sessionApp = angular.module('sessionApp', []);
DotNetNuke Module Settings
The Session Timer dialog needs a setting to tell it when to open. One way to store this value is to create a DNN Module Setting for it so it can be easily changed from within the CMS. If you used Christoc’s DotNetNuke Module Development Template to create your module, you should have a Settings module control stubbed out and ready for you to add the Session Timer Threshold setting controls so the value in minutes can be stored as a module setting in the DNN database. The Settings module control markup and code for this is below.
Settings.ascx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Settings.ascx.cs" Inherits="DotNetNuke.Modules.Angular.Settings" %>
<%@ Register TagName="label" TagPrefix="dnn" Src="~/controls/labelcontrol.ascx" %>
<h2 id="dnnSitePanel-BasicSettings" class="dnnFormSectionHead"><a href="" class="dnnSectionExpanded"><%=LocalizeString("BasicSettings")%></a></h2>
<fieldset>
<div class="dnnFormItem">
<dnn:Label ID="lblSessionTimerThreshold" runat="server" />
<asp:TextBox ID="txtSessionTimerThreshold" runat="server" />
</div>
</fieldset>
Settings.ascx.cs
using System;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Services.Exceptions;
namespace DotNetNuke.Modules.Angular
{
public partial class Settings : AngularModuleSettingsBase
{
#region Base Method Implementations
/// -----------------------------------------------------------------------------
///
/// LoadSettings loads the settings from the Database and displays them
///
/// -----------------------------------------------------------------------------
public override void LoadSettings()
{
try
{
if (Page.IsPostBack == false)
{
if (Settings.Contains("SessionTimerThreshold"))
txtSessionTimerThreshold.Text = Settings["SessionTimerThreshold"].ToString();
}
}
catch (Exception exc) //Module failed to load
{
Exceptions.ProcessModuleLoadException(this, exc);
}
}
/// -----------------------------------------------------------------------------
///
/// UpdateSettings saves the modified settings to the Database
///
/// -----------------------------------------------------------------------------
public override void UpdateSettings()
{
try
{
var modules = new ModuleController();
modules.UpdateModuleSetting(ModuleId, "SessionTimerThreshold", txtSessionTimerThreshold.Text);
}
catch (Exception exc) //Module failed to load
{
Exceptions.ProcessModuleLoadException(this, exc);
}
}
#endregion
}
}
Settings.ascx.resx
To make use of the label control (dnn:Label) that is registered at the top of the Settings module control file, edit the respective resource (.resx) file as shown in the image below. The label control provides label text, and tool-tip text for additional information.
Settings.ascx.resx
for our SessionTimer
Threshold value in the Angular module settings form
Add a SessionTimerJavaScript
method to the AngularModuleBase
class so all module controls in the Angular module have access to it. This method loads the SessionTimer.js
and AngularJs
module constants for the dialog display threshold and session duration. Here is what the file looks like with this method added.
AngularModuleBase.cs
using DotNetNuke.Entities.Modules;
namespace DotNetNuke.Modules.Angular
{
public class AngularModuleBase : PortalModuleBase
{
public static void SessionTimerJavaScript(PortalModuleBase portalModuleBase)
{
if (portalModuleBase.Page != null)
{
string timerScript = "";
int sessionWarningThresholdInMilliSeconds = int.Parse(portalModuleBase.Settings["SessionTimerThreshold"].ToString()) * 60000;
int sessionDurationInMilliSeconds = portalModuleBase.Page.Session.Timeout * 60000;
//guard against invalid warning threshold.
if (sessionWarningThresholdInMilliSeconds >= sessionDurationInMilliSeconds)
{
sessionWarningThresholdInMilliSeconds = sessionDurationInMilliSeconds - 1000;
}
string timerVars = string.Format("sessionApp.constant('settings',{{ threshold: {0}, sessionDuration: {1} }});\n", sessionWarningThresholdInMilliSeconds.ToString(), sessionDurationInMilliSeconds.ToString());
if (!portalModuleBase.Page.ClientScript.IsStartupScriptRegistered("_SessionTimerInclude_"))
{
portalModuleBase.Page.ClientScript.RegisterStartupScript(portalModuleBase.Page.GetType(), "_SessionTimerInclude_", timerScript, false);
}
if (!portalModuleBase.Page.ClientScript.IsStartupScriptRegistered("_SessionTimerVars_"))
{
portalModuleBase.Page.ClientScript.RegisterStartupScript(portalModuleBase.Page.GetType(), "_SessionTimer_", timerVars, true);
}
}
}
}
}
For now, edit the Angular module settings entering a value of 18 for the session timer threshold. This will make it so the session dialog opens only after 2 minutes with a default session duration of 20 minutes. Typically, you will want to use a value of 2 for the threshold, but we don’t want to have to wait 18 minutes for the dialog to open while we test it out.
Add the SessionTimerJavaScript
method call to your View module control OnLoad method as follows.
View.ascx.cs
protected override void OnLoad(EventArgs e)
{
DotNetNuke.Framework.jQuery.RequestUIRegistration();
//load AngularJS Session Timer
SessionTimerJavaScript(this);
}
Build the module and refresh the page in DNN. When you view the page source in the browser, you should see the following near the bottom of the page if your session timer threshold is set to 18 minutes and session duration is the default of 20 minutes:
HTML view source
<script src="/DesktopModules/Angular/Scripts/SessionTimer.js" type="text/javascript">/script>
<script type="text/javascript">
//<![CDATA[
sessionApp.constant('settings',{ threshold: 1080000, sessionDuration: 1200000 });
//]]>
</script>
jQuery UI Dialog
Add this jQuery UI markup to the bottom of the default (View) module control.
View.ascx
<div ng-app="sessionApp" ng-controller="SessionCtrl">
<div id="dialog-session" title="Session Expiration">
<p>Your session will expire in:</p>
<p>{{ remainingTime | minute }} minutes.</p>
</div>
</div>
</div>
Add this css to the module stylesheet so the dialog is hidden when the View module control loads.
module.css
#dialog-session {
display: none;
}
Session Timer AngularJS Timeout
Below the sessionApp
angular module variable that was added last time we touched this JavaScript file, add the variable for the jQuery UI dialog selector, sessionAppTimeoutId
var, sessionApp
filter for dialog minutes display and sessionApp
controller code so you file looks like this:
SessionTimer.js
'use strict';
var sessionAlert = $("#dialog-session");
/* module def */
var sessionApp = angular.module('sessionApp', []);
var sessionAppTimeoutId;
/* filters */
sessionApp.filter('minute', function () {
return function (milliseconds) {
var seconds = Math.floor(milliseconds / 1000);
var minutes = Math.floor(seconds / 60);
seconds = seconds % 60;
return minutes + ":" + (seconds < 10 ? "0" + seconds : seconds);
}
});
/* controllers */
sessionApp.controller('SessionCtrl', ['$scope', '$timeout', '$window', 'settings', function ($scope, $timeout, $window, settings) {
$scope.checkInterval = 1000;
$scope.threshold = settings.threshold;
$scope.duration = $scope.remainingTime = settings.sessionDuration;
$scope.hide = function () {
sessionAlert.dialog("close");
}
$scope.displayDialog = function () {
//remove logging in production
console.log('displayDialog');
sessionAlert.dialog({
dialogClass: "dnnFormPopup",
modal: true,
resizable: false,
buttons: [{
text: "Extend Session",
click: function () {
$window.location.reload();
}
}]
});
}
$scope.checkSession = function () {
$timeout.cancel(sessionAppTimeoutId);
$scope.remainingTime = $scope.remainingTime - $scope.checkInterval;
//remove logging in production
console.log($scope.remainingTime);
if ($scope.remainingTime === settings.threshold) {
$scope.displayDialog();
}
if ($scope.remainingTime > 0) {
sessionAppTimeoutId = $timeout($scope.checkSession, $scope.checkInterval);
} else {
sessionAlert.dialog("option", "buttons", [{ text: "Ok", click: function () { $window.location.reload(); } }]);
}
}
$scope.start = function () {
sessionAppTimeoutId = $timeout($scope.checkSession, $scope.checkInterval);
}
$scope.start();
}]);
Forms Authentication (added July 10, 2013)
The default DNN installation uses Forms Authentication that will log out the user after 60 minutes of inactivity as shown in this web.config
node:
web.config
<authentication mode="Forms">
<forms name=".DOTNETNUKE" protection="All" timeout="60" cookieless="UseCookies" />
</authentication>
To make use of the forms timeout since that overrides the Session duration value we are currently using, in AngularModuleBase.cs:
Replace
int sessionDurationInMilliSeconds = portalModuleBase.Page.Session.Timeout * 60000;
With
int formsTimeoutDurationInMilliSeconds = System.Web.Security.FormsAuthentication.Timeout.Milliseconds;
Refactor the respective sessionDurationInMilliSeconds var names in the remainder of the SessionTimerJavaScript method with the new name: formsTimeoutDurationInMilliSeconds.
This project is available for browsing and download at GitHub:
https://github.com/jimfrenette/DnnAngular
To Do List…
- Check permissions and if the page is public, do not the start the Session Timer.
- Add validation to the Session Timer Threshold input so its value cannot be set higher then the session duration.