DNN Windows Client

This tutorial will walk you through the creation of a basic Windows Forms application to test remote authentication and authorization when communicating with a DNN CMS web portal using its web services framework / Web API. This is for development and educational purposes only running locally. You will at least want to make sure that you have SSL enabled for your DNN Web Services in a real world scenario.

Requirements

Create a New Windows Forms Project

DnnWinClient - Windows Forms Project
DnnWinClient – Windows Forms Project

While in the Form1.cs[Design] workspace, open the Toolbox and drag a TextBox and Label over to Form1 as shown below

Form1 - Add Controls
Form1 – Add Controls

Select the TextBox and then select F4 to bring up its Properties. Change the Name of the TextBox control to txtSite as shown below. Select the Label control, bring up its Properties, and Name it lblSite, set its Text to Site.

Form1 - TextBox Properties
Form1 – TextBox Properties

Repeat the steps adding these controls to the form and setting the respective Properties of each as needed:

    Labels

  • Name: lblUsername
    Text: Username
  • Name: lblPassword
    Text: Password
  • Name: lblRoute
    Text: Route
  • Name: lblResponse
    Text: Response
    Text Boxes

  • Name: txtUsername
  • Name: txtPassword
    UseSystemPasswordChar: True
  • Name: txtRoute
  • Name: txtResponseMultiline: True

For simplicity, we will just set the Text property of the Text Box controls to display our default values.

    Defaults

  • Name: txtSite
    Text: http://dnn7 (your DNN website Url)
  • Name: txtRoute
    Text: DesktopModules/Services/API/TestAuth

Add a Button control so we can make a connection to the DNN CMS from our Windows Client. Name: txtButton, Text: Connect. Almost done with the design, select the form, and set its Text to DNN Client. Position and resize the Form, Labels, Text Boxes and Button to your liking making sure you have given the txtResponse Text Box enough space to display the response from the DNN web service. Here is how my form looks after some dragging and sizing of the form and its controls.

DnnWinClient - Form1.cs[Design]
DnnWinClient – Form1.cs[Design]

Note the expanded Form1 node in Solution Explorer, the order of the controls is determined by their TabIndex property value.

If everything looks good, now is a good time to Save All (Ctrl+Shift+S) and Build (Ctrl+Shift+B) your Solution.

RestSharp

RestSharp is a Simple REST and HTTP API client for .NET that makes building the requests easy and efficient. To install RestSharp into the solution, I prefer NuGet. From the menu In Visual Studio, select Tools > Library Package Manager > Package Manager Console. Then run the following command:

PM> Install-Package RestSharp

Connect Code

Double click on the Connect button (btnConnect) to stub out a btnConnect_Click method in the Form1 class. Copy and Paste the following code into the method:

var domain = txtSite.Text;
var username = txtUsername.Text;
var password = txtPassword.Text;
var route = txtRoute.Text;

var apiUrl = string.Format("{0}/{1}", domain, route);

IRestRequest request = new RestRequest();
var client = new RestClient
{
    BaseUrl = apiUrl,
    Authenticator = new HttpBasicAuthenticator(username, password),
};
IRestResponse response = client.Execute(request);

txtResponse.Text = response.Content;

After removing the unused usings, adding RestSharp and a application/json request header, here is what the entire class should look like:

using System;
using System.Windows.Forms;
using RestSharp;

namespace DnnWinClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            var domain = txtSite.Text;
            var username = txtUsername.Text;
            var password = txtPassword.Text;
            var route = txtRoute.Text;

            var apiUrl = string.Format("{0}/{1}", domain, route);

            IRestRequest request = new RestRequest();
            //We want our response in Json format
            request.AddHeader("Content-type", "application/json; charset=utf-8");
            var client = new RestClient
            {
                BaseUrl = apiUrl,
                Authenticator = new HttpBasicAuthenticator(username, password),
            };
            IRestResponse response = client.Execute(request);

            txtResponse.Text = response.Content;
        }
    }
}

Save All (Ctrl+Shift+S) and Build (Ctrl+Shift+B) the Solution. We our now done with our simple DNN Windows Client. Now let’s move onto the web services class library.

Setup DNN Web Services

DotNetNuke (DNN) will need a Web API service endpoint created to allow the Windows Client to communicate with the CMS. See this blog post for info on how to build a DNN Web Services Framework class library.

RouteMapper.cs
using System;
using System.Web.Http;
using DotNetNuke.Web.Api;

namespace WebServices
{
    public class RouteMapper : IServiceRouteMapper
    {
        public void RegisterRoutes(IMapRoute mapRouteManager)
        {
            mapRouteManager.MapHttpRoute(
                moduleFolderName: "Services",
                routeName: "Default",
                url: "{controller}/{id}",
                defaults: new { id = RouteParameter.Optional },
                namespaces: new[] { "WebServices" }
            );
        }
    }
}

Create a model for the data we are returning to the client. In the Solution Explorer, right click on the WebServices project and select Add > New Folder and name it Models. Add this class to the Models folder:

Models.User.cs
using System;

namespace WebServices.Models
{
    class User
    {
        public string FirstName { get; set; }
        public string Email { get; set; }
        public bool IsSuperUser { get; set; }
        public string LastName { get; set; }
        public int PortalID { get; set; }
        public int UserID { get; set; }
    }
}

Create a DNN API controller for the endpoint that returns the model data to the client. In the Solution Explorer, right click on the WebServices project and select Add > Class and name it TestAuthController.

TestAuthController.cs
using System;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using DotNetNuke.Web.Api;

namespace WebServices
{
    public class TestAuthController : DnnApiController
    {
        [DnnAuthorize]        
        public HttpResponseMessage Get()
        {
            //map UserInfo to User object we are returning
            Models.User user = new Models.User
            {
                Email = this.UserInfo.Email,
                IsSuperUser = this.UserInfo.IsSuperUser,
                FirstName = this.UserInfo.FirstName,
                LastName = this.UserInfo.LastName,
                PortalID = this.UserInfo.PortalID,
                UserID = this.UserInfo.UserID
            };
            return Request.CreateResponse(HttpStatusCode.OK, user);
        }

    }
}

Build the project making sure that the output is set to your DNN website root bin folder. For more information, refer to this blog post DotNetNuke 7. Open up your local DNN website in a browser to setup the DNN Web Services you just built.

Now for the moment of truth, Start the DnnWinClient and enter your DNN Username, Password and click Connect. The Response should contain the expected user info.

DnnWinClient - Running
DnnWinClient – Running
Troubleshooting

If the PortalID and UserID values are -1 and the other values are null, check your DNN website web.config modules node. You may need the runAllManagedModulesForAllRequests attribute set to true. In theory it has a performance impact, in practice, IIS should already be doing this. For more info, refer to this blog post by Rick Strahl: ASP.NET Routing not working on IIS 7.0

web.config – Replace
<modules>
web.config – With
<modules runAllManagedModulesForAllRequests="true">

This project is available for browsing and download at GitHub: https://github.com/jimfrenette/DnnWinClient

Resources

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.cofig 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

DotNetNuke 7 Web Services Framework Web API Routes


When building the DotNetNuke Web Services API controllers, I want to use this REST convention for the endpoints to access a resource such as the Chinook database. Note: My DotNetNuke website is running under IIS 7.5 with "dnn7" as the host name binding. Only the request method, url, protocol, content-type and content-body are included in request examples below.

Return all Tracks:

GET http://dnn7/DesktopModules/Services/API/Tracks HTTP/1.1

Return a Track where ID = 1:

GET http://dnn7/DesktopModules/Services/API/Tracks/1 HTTP/1.1
CREATE a Track:
POST http://dnn7/DesktopModules/Services/API/Tracks HTTP/1.1

Content-Type: application/json

{
    "albumId": 1,
    "name": "For Those About To Rock (We Salute You)",
    "mediaTypeId": 1,
    "genreId": 1,
    "composer": "Angus Young, Malcolm Young, Brian Johnson",
    "milliseconds": 343719,
    "bytes": 11170334,
    "unitPrice": 0.99
}
UPDATE a Track:
PUT http://dnn7/DesktopModules/Services/API/Tracks/1 HTTP/1.1

Content-Type: application/json

{
    "albumId": 1,
    "name": "For Those About To Rock (We Salute You)",
    "mediaTypeId": 1,
    "genreId": 1,
    "composer": "Angus Young, Malcolm Young, Brian Johnson",
    "milliseconds": 343719,
    "bytes": 11170334,
    "unitPrice": 0.99
}
DELETE a Track where ID = 1:
DELETE http://dnn7/DesktopModules/Services/API/Tracks/1 HTTP/1.1
DELETE Tracks – body contains a json array of Track ID’s to delete:
DELETE http://dnn7/DesktopModules/Services/API/Tracks/Delete HTTP/1.1

Content-Type: application/json

{
    "ids": [
        6,
        7,
        8
    ]
}
RouteMapper.cs
using System;
using System.Web.Http;
using DotNetNuke.Web.Api;

namespace WebServices
{
    public class RouteMapper : IServiceRouteMapper
    {
        public void RegisterRoutes(IMapRoute mapRouteManager)
        {
            mapRouteManager.MapHttpRoute(
                moduleFolderName: "Services",
                routeName: "Default",
                url: "{controller}/{id}",
                defaults: new { id = RouteParameter.Optional },
                namespaces: new[] { "WebServices" }
            );
        }
    }
}

Json.NET

For the Delete method in the TracksController, we will use Json.NET to parse the track id’s from the json payload. To install Json.NET into the solution, I prefer NuGet. From the menu In Visual Studio, select Tools > Library Package Manager > Package Manager Console. Then run the following command:

PM> Install-Package Newtonsoft.Json
TracksController.cs
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using DotNetNuke.Web.Api;
using Newtonsoft.Json.Linq;

namespace WebServices
{
    [AllowAnonymous]
    public class TracksController : DnnApiController
    {
        public HttpResponseMessage Get()
        {
            //put your code here for call to handle the get
            //for example
            //tracks = TracksService.Get();

            return Request.CreateResponse(HttpStatusCode.OK, tracks);
        }

        public HttpResponseMessage Get(int id)
        {
            //put your code here for call to handle the get
            //for example
            //track = TracksService.Get(id);

            return Request.CreateResponse(HttpStatusCode.OK, track);
        }

        [DnnAuthorize]
        public HttpResponseMessage Post(Track track)
        {
            //put your code here for call to handle the create
            //for example
            //TracksService.Create(track);

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        [DnnAuthorize]
        public HttpResponseMessage Put(Track track)
        {
            //put your code here for call to handle the update
            //for example
            //TracksService.Update(track);

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        [DnnAuthorize]
        public HttpResponseMessage Delete(int id)
        {
            //put your code here for call to handle the delete
            //for example
            //TracksService.Delete(id);

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        [DnnAuthorize]
        public HttpResponseMessage Delete(JObject jObject)
        {
            var ids = new JArray();
            if (jObject["ids"].Type == JTokenType.String)
            {
                ids.Add(jObject["ids"]);
            }
            else
            {
                ids = (JArray)jObject["ids"];
            }
            long[] assetIds = ids.Select(jv => Convert.ToInt64((string)jv)).ToArray();
            
            //put your code here for call to handle the delete
            //for example
            //TracksService.Delete(assetIds);

            return Request.CreateResponse(HttpStatusCode.OK);
        }

    }

}

Resources

DotNetNuke 7 Module Templates


I wanted to stick with the environment setup I have been using which is a host name of dnn7 or dnndev. Working in IIS and modifying my host file is not an issue for me. If you are looking for a way to create a new module directly within the DotNetNuke CMS environment, you could give this extension a try: DNN Module Creator.

Download Source Code for Christoc’s DotNetNuke Module Development Template at
http://christoctemplate.codeplex.com/

This post is based on the DotNetNuke 7 Project Templates V2.0 for VS2012, Thu Jan 24, 2013. Newer 2.x versions of this template project may have somewhat different node or node block keys in the csproj files for the environment variables we are modifying in this guide. The premise is still the same and this information should be enough to help make the changes as needed.

You will need this to modify and rebuild the source: Microsoft Visual Studio 2012 SDK http://www.microsoft.com/en-us/download/details.aspx?id=30668
Filename: vssdk_full.exe

    Modified these two template project files

  • CSharp-Template.csproj
  • CSharp-DAL2-Template.csproj
    In these two nodes

  • WebProjectProperties/IISUrl
  • WebProjectProperties/IISAppRootUrl

Replace
$devenvironmenturl$
with
dnndev

REPLACED —————–

<WebProjectProperties>
  <UseIIS>True</UseIIS>
  <AutoAssignPort>True</AutoAssignPort>
  <DevelopmentServerPort>62640</DevelopmentServerPort>
  <DevelopmentServerVPath>/</DevelopmentServerVPath>
  <IISUrl>http://$devenvironmenturl$/desktopmodules/$safeprojectname$</IISUrl>
  <OverrideIISAppRootUrl>True</OverrideIISAppRootUrl>
  <IISAppRootUrl>http://$devenvironmenturl$/</IISAppRootUrl>
  <NTLMAuthentication>False</NTLMAuthentication>
  <UseCustomServer>False</UseCustomServer>
  <CustomServerUrl>
  </CustomServerUrl>
  <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>

WITH ———————

<WebProjectProperties>
  <UseIIS>True</UseIIS>
  <AutoAssignPort>False</AutoAssignPort>
  <DevelopmentServerPort />
  <DevelopmentServerVPath />
  <IISUrl>http://dnndev/desktopmodules/$safeprojectname$</IISUrl>
  <OverrideIISAppRootUrl>True</OverrideIISAppRootUrl>
  <IISAppRootUrl>http://dnndev</IISAppRootUrl>
  <NTLMAuthentication>False</NTLMAuthentication>
  <UseCustomServer>False</UseCustomServer>
  <CustomServerUrl>
  </CustomServerUrl>
  <SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>

I don’t use the Visual Basic templates so I left those projects AS IS

I needed to also update the MSBuildTasks version numbers in the templates. For the version 1.4.0.56 of MSBuildTasks I installed today from nuget using the Package Manager Console in Visual Studio
[code][/code]PM> Install-Package MSBuildTasks[code][/code]

Opened Template Solution
DNNTemplates.sln
Searched Entire Solution

Find in Files
MSBuildTasks.1.4.0.45

Replace with
MSBuildTasks.1.4.0.56

SAVE ALL / REBUILD SOLUTION

Resources

DotNetNuke 7

A few days ago I installed DotNetNuke (DNN) 7.0.0 to mainly test the new version of it’s Services Framework that is based on the Web API stack.

Installation

You can download the DotNetNuke 7 Community Edition installation archive here.

Unblock the archive and extract it to a new directory under your IIS inetpub folder. For example, c:\inetpub\dnn7.

icacls c:\inetpub\dnn7 /t /Grant NetworkService:F

OPEN Internet Information Services Manager and create a new IIS application that is rooted at the DNN path. Here is an example of the applications advanced settings.

DNN 7 IIS application advanced settings

The DNN 7 application pool runs under .NET 4.0 and uses the Network Service profile. Under the application pool advanced settings, change Process Model > Identity to NetworkService

DNN 7 IIS application pool advanced settings

Map the application hostname to the localhost IP address. OPEN notepad (run as administrator), modify your hosts file in C:\Windows\System32\Drivers\etc. For example, add this line for an application with a host named dnn7:

127.0.0.1       dnn7

Create a blank SQL Server database. For example, using SQL SERVER Express 2012, I logged in using the sa account and created a new database named DNN_7. NOTE: When the SQL Server is installed using only Windows Authentication, the sa login is disabled. To enable the sa account, see Change Server Authentication Mode.

To complete the installation, restart the application in IIS and load the host you specified in the hosts file in a web browser. For example, http://dnn7

After completing the install, the new DNN 7 loaded quickly and looked nice. First thing I tried to do was create a page and add a module to it. While logged in as host, when I click on the new “Edit This Page” button, the page refreshes and I’m still in View mode. After contacting DNN support, I was instructed to change the control panel to RIBBONBAR, the old control bar from DNN 6:

Host > Host Settings > Other Settings, change the Control Panel from CONTROLBAR to RIBBONBAR

After the change, I was able to toggle the page into Edit mode. According to DNN support, this issue was actually reported during the Beta, thought to be fixed, then popped up again during the release. Their developers are hard at work trying to resolve it for DNN 7.0.1. Unlike my experience, you may not have any issues at all with the new DNN 7 CONTROLBAR control panel. The developers have narrowed it down to something in IIS, however they are not sure exactly which configuration. My configuration that has this problem is IIS Version 7.5.7600.16385 on Windows 7 Professional. The new DNN 7 CONTROLBAR control panel worked as expected in DotNetNuke 7 on Windows 8 Professional, IIS Version 8.0.9200.16384

Services Framework

Clone the master branch of this GitHub repository that Scott Schlieser had built for a session at 2012 Charlotte Day of DotNetNuke. At the time of this post, the master branch contained the revised DNN 7.0 Web API based implementation.

DNN Services Framework ExploreSettings Module

Creating your own DNN WebServices class library

In Visual Studio, create a new C# .NET Framework 4 Class Library Project
dnn-web-services-proj-create

This library project does not need to be included within the DesktopModules folder or for that matter the DNN website. However, it’s build needs to be in the DNN root bin. I prefer to locate the class library in the DesktopModules folder keeping all my source and module code together. Next, right click on the newly created WebServices project, select Properties and set the build output path to the DNN root bin for all configurations as shown in this image.

dnn-web-services-build-output

In the Solution Explorer, right click on the References node and select Add Reference to open the Reference Manager.

dnn-web-services-add-reference

Browse to the root bin folder which in this example is C:\inetpub\dnn7\bin to select and add the following dll’s:

  • DotNetNuke
  • DotNetNuke.Web
  • System.Net.Http
  • System.Net.Http.Formatting
  • System.Web.Http

Next, under Assemblies > Framework, select and add this dll

  • System.Web

Your References should now look something like this in Solution Explorer

dnn-web-services-references

Next, create a class to Register Routes. the project template created a Class named Class1. Let’s rename Class1.cs to RouteMapper.cs and allow Visual Studio to also rename the class when prompted. At the very top of the class, replace all the usings with just these two:

  • using System;
  • using DotNetNuke.Web.Api;

The RouteMapper class also needs to inherit the IServiceRouteMapper interface. After adding that your empty RouteMapper class should look like this:

using System;
using DotNetNuke.Web.Api;

namespace WebServices
{
    public class RouteMapper : IServiceRouteMapper
    {
    }
}

Now add the RegisterRoutes method to our RouteMapper class as follows to map a route for the controller we will create next.

using System;
using DotNetNuke.Web.Api;

namespace WebServices
{
    public class RouteMapper : IServiceRouteMapper
    {
        public void RegisterRoutes(IMapRoute mapRouteManager)
        {
            mapRouteManager.MapHttpRoute("Services", "default", "{controller}", new[] { "WebServices" });
        }
    }
}

Add a new public class named TestController.cs that inherits the DnnApiController class and contains a Get action as follows:

using System;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using DotNetNuke.Web.Api;

namespace WebServices
{
    public class TestController : DnnApiController
    {
        [AllowAnonymous]
        public HttpResponseMessage Get()
        {
            return Request.CreateResponse(HttpStatusCode.OK, "Hello From DotNetNuke!");
        }
    }
}

Now build the project and verify that there is a WebServices.dll in the DotNetNuke root bin. Test the service by loading the endpoint url in a browser like http://dnn7/DesktopModules/Services/API/Test

Other Resources

DotNetNuke Modal PopUps

Task 1, create a jQuery UI dialog to confirm a postback action invoked by a ASP.NET button control.

In your module’s ascx file, add a button control and a div to contain you dialog.

<asp:Button ID="btnDelete" runat="server"
	AutoPostBack="false"
	ButtonType="LinkButton"
	CssClass="main_button"
	OnClick="btnDelete_Click"
	Text="Delete"
	UseSubmitBehavior="false">
</asp:Button>

<div id="dlgDeleteConfirm" title="Confirm">
	<div class="confirmDialog">Are you sure you want to delete this?</div>
</div>

In you ascx code behind, create a method to create a client script block, you call can call on page load. This modular approach of passing server vars to the client is one I like the best. With this method you can modify the code to pass in arguments and also include them when writing your script block, etc..

private void Page_Load(object sender, System.EventArgs e)
{
	ClientScriptBlock();
}

private void ClientScriptBlock()
{
	Type csType = this.GetType();
	ClientScriptManager cs = Page.ClientScript;
	if (!cs.IsStartupScriptRegistered(csType, "Vars"))
	{
		StringBuilder sb = new StringBuilder();
		sb.Append("");
		cs.RegisterClientScriptBlock(csType, "Vars", sb.ToString());
	}
}

protected void btnDelete_Click(object sender, EventArgs e)
{
 //delete method followed by redirect or whatever on success
}
.confirmDialog {
	padding: 15px 15px 0 15px;
	font-weight: bold;
}

jQuery – I had trouble getting .dnnConfirm to handle the postback with it’s isButton option, so I used a standard jQuery UI Dialog instead.

jQuery(document).ready(function ($) {
	$("#dlgDeleteConfirm").dialog({
		autoOpen: false,
		modal: true,
		width: 300,
		height: 140,
		resizable: false,
		dialogClass: "dnnFormPopup",
		buttons: {
			"Yes": function() {<%=this.Page.ClientScript.GetPostBackEventReference(new PostBackOptions(this.btnDelete))%>;},
			"No": function() {$(this).dialog('close');}
		}
	});
	$("#" + btnDelete_ClientID).click(function () {
		$("#dlgDeleteConfirm").dialog('open'); return false;
	});
});

NOTE: You need to make sure that DNN loads the jQuery UI in either OnInit or OnLoad in your Module Controls code behind.

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);

    //register jQuery-UI - no need to register jQuery
    //since jQuery-UI registration method will handle this 
    DotNetNuke.Framework.jQuery.RequestUIRegistration();
}

Task 2, create a jQuery UI dialog to contain an Edit form

Resources

DotNetNuke CSS and JS Module Development – Part One

During module development, to see all of the js and css files in firebug or your favorite client debugging tool of choice, turn off the file combination provider. In this section at or near the bottom of your web.config, set the provider enableCompositeFiles attributes from true to false as shown in the example below.

<clientDependency version="2" fileDependencyExtensions=".js,.css">
  <fileRegistration defaultProvider="LoaderControlProvider">
    <providers>
      <add name="DnnBodyProvider" type="DotNetNuke.Web.Client.Providers.DnnBodyProvider, DotNetNuke.Web.Client" enableCompositeFiles="false" />
      <add name="DnnFormBottomProvider" type="DotNetNuke.Web.Client.Providers.DnnFormBottomProvider, DotNetNuke.Web.Client" enableCompositeFiles="false" />
      <add name="PageHeaderProvider" type="ClientDependency.Core.FileRegistration.Providers.PageHeaderProvider, ClientDependency.Core" enableCompositeFiles="false" />
      <add name="LazyLoadProvider" type="ClientDependency.Core.FileRegistration.Providers.LazyLoadProvider, ClientDependency.Core" enableCompositeFiles="false" />
      <add name="LoaderControlProvider" type="ClientDependency.Core.FileRegistration.Providers.LoaderControlProvider, ClientDependency.Core" enableCompositeFiles="false" />
      <add name="DnnPageHeaderProvider" type="DotNetNuke.Web.Client.Providers.DnnPageHeaderProvider, DotNetNuke.Web.Client" enableCompositeFiles="false" />
    </providers>
  </fileRegistration>
  <compositeFiles defaultFileProcessingProvider="CompositeFileProcessor" compositeFileHandlerPath="~/DependencyHandler.axd">
    <fileProcessingProviders>
      <add name="CompositeFileProcessor" type="ClientDependency.Core.CompositeFiles.Providers.CompositeFileProcessingProvider, ClientDependency.Core" enableCssMinify="false" enableJsMinify="true" persistFiles="true" compositeFilePath="~/App_Data/ClientDependency" bundleDomains="" urlType="MappedId" />
    </fileProcessingProviders>
  </compositeFiles>
</clientDependency>

Here is another web.config change to speed up the page load after a new build.

Change

<compilation debug="true" strict="false">

To

<compilation debug="true" strict="false" optimizeCompilations="true">

Resources

DotNetNuke Module Control Options

You could use a Multi-View inside of your Edit.ascx to create a dynamic way to toggle visible form/content objects and their code within a single Web User Control ascx and it’s ascx.cs code file. This is a popular approach when working inside the Edit control since dynamically loaded sub-controls will not have persisted postback data.

You could also add a new parent ascx file all together:

Add a New Web User Control

References:
Copy Edit.ascx code behind using ... to overwrite the new code behind using ...

Replace class inheritence
 : System.Web.UI.UserControl
With
 : [MyModuleName]ModuleBase

EXAMPLE
public partial class EditMyAsset : System.Web.UI.UserControl

public partial class EditMyAsset : MyModuleNameModuleBase

In App_LocalResources
Copy Edit.ascx.resx To EditMyAsset.ascx.resx

Add Module Definition:
- Open Edit Extension Form
  Host | Extensions | MyModuleName > Edit Extension
- Navigate to
  Module Definitions | Add Module Control

  Add a Key "EditMyAsset"
  Select the Source path "DesktopModules/MyModuleName/EditMyAsset.ascx"
  Select Type: "Edit"

- Open MyModuleName.dnn
  Copy the node where controlKey = "Edit" //moduleDefinitions/moduleControls/moduleControl
  Change the controlKey in the new node to "EditMyAsset"
  Change the controlSrc in the new node to "DesktopModules/MyModuleName/EditMyAsset.ascx"

Dynamically Loading Web User Controls

If this is View Control that will not have form postbacks, then it is okay to dynamically load the Web User Controls.

aspx code
<%@ Control language="C#" Inherits="DotNetNuke.Modules.MyModuleName.View" AutoEventWireup="false"  Codebehind="View.ascx.cs" %>

<asp:PlaceHolder id="phControls" runat="server"></asp:PlaceHolder>
aspx.cs code
using System;
using DotNetNuke.Common;
using DotNetNuke.Services.Exceptions;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Modules.Actions;
using DotNetNuke.Services.Localization;
using DotNetNuke.Security;


namespace DotNetNuke.Modules.MyModuleName
{

    /// -----------------------------------------------------------------------------
    /// 
    /// The ViewMyModuleName class displays the content
    /// 
    /// -----------------------------------------------------------------------------
    public partial class View : MyModuleNameModuleBase, IActionable
    {

        #region Event Handlers

        override protected void OnInit(EventArgs e)
        {
            InitializeComponent();
            base.OnInit(e);
            DotNetNuke.Framework.jQuery.RequestDnnPluginsRegistration();
        }

        private void InitializeComponent()
        {
            this.Load += new System.EventHandler(this.Page_Load);
        }


        /// -----------------------------------------------------------------------------
        /// 
        /// Page_Load runs when the control is loaded
        /// 
        /// -----------------------------------------------------------------------------
        private void Page_Load(object sender, System.EventArgs e)
        {
            try
            {
                //load the User Controls based on QueryString params
                string query = Request.QueryString["q"];

                switch (query)
                {
                    case "album-grid":
                        SetControl("Controls/AlbumGrid.ascx");
                        break;
                    case "artist-grid":
                        SetControl("Controls/ArtistGrid.ascx");
                        break;
                    default:
                        SetControl("Controls/Default.ascx");
                        break;
                }
            }
            catch (Exception exc) //Module failed to load
            {
                Exceptions.ProcessModuleLoadException(this, exc);
            }

        }

        private void SetControl(string ControlPath)
        {
            PortalModuleBase b = (PortalModuleBase)LoadControl(ControlPath);
            b.ModuleConfiguration = this.ModuleConfiguration;
            b.ID = System.IO.Path.GetFileNameWithoutExtension(ControlPath);
            phControls.Controls.Add(b);
        }

        #endregion

        #region Optional Interfaces

        public ModuleActionCollection ModuleActions
        {
            get
            {
                ModuleActionCollection Actions = new ModuleActionCollection();
                Actions.Add(GetNextActionID(), Localization.GetString("EditModule", this.LocalResourceFile), "", "", "", EditUrl(), false, SecurityAccessLevel.Edit, true, false);
                return Actions;
            }
        }

        #endregion

    }

}

Additional Resources