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
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
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…

  1. Check permissions and if the page is public, do not the start the Session Timer.
  2. Add validation to the Session Timer Threshold input so its value cannot be set higher then the session duration.

Resources

comments powered by Disqus