05 December 2008

Merge two sorted integer arrays

My current programming task is to merge two sorted arrays of integers into one array while removing duplicate integers. There are no duplicates in the source arrays A and B. The source arrays can be altered. The integers in each array may tend to be in blocks which don't overlap, but this isn't guaranteed. The arrays may currently have up to 100,000 integers but will often have less than this. The output array should be truncated to 100,000 elements. I'm using C# .NET but that's irrelevant really - it's just the algorithm that's important.

I came up with several possible solutions, and am currently trying the last. What would you do?

1.
Walk through A and B simultaneously. If an integer in B exists in A then ignore it; if it doesn't exist then add it to the end of A.
At the end of this process, call Array.Sort on A (ie a QuickSort).
(Note that inserting a B element into A would surely take too long as it would involve moving all other A entries up.)
Quite efficient but I am concerned at speed of final sort.

2.
Create a new linked list with all the A entries.
Walk through the linked list and B simultaneously; for integers in B that need inserting, alter the linked list to add a new entry in the correct place.
At end, convert linked list back into an array.
Not very efficient creating a linked list and converting back from linked list.

3.
Walk through A and B simultaneously. Create a list structure where each element represents a block of integers in A or B, eg use integers 0-4 from A, integers 3-15 from B, integers 5-10 from A, etc.
At the end, create a new array from the list structure.
This is a more complicated version of my final simpler solution.

4.
Not quite sure if I thought this through properly.
Walk through A and B simultaneously. If the current integer in B is less than the current integer in A, then swap the two. This sounds simple but in fact the integer swapped into B will now possibly be out of order in B, so the element will need bubbling up until it finds its correct place.
At the end, simply add the remaining B elements to A.
Superficially attractive but more complicated than it looks.

5.
Walk through A and B simultaneously creating a new table C as you go.
Simple

Unless you can suggest otherwise...

03 September 2008

Writing DNN custom modules

This is the last of three blog entries that make up a full introduction to DotNetNuke (DNN) from a developer's point of view. This time I cover Writing Custom Modules to add your own functionality to a DNN web site. Previously I gave an Introduction to DNN and showed how to Write a simple DNN Skin. A concise version of the complete article first appeared on 1 July 2008 in UK programming magazine VSJ.

The phdcc.CodeModule module described in this article can be downloaded from here.

DNN5 has a new extension/package structure for custom modules and skins - however 'legacy' DNN3 and DNN4 module zips will install in DNN 5.

Overview

Custom DNN modules can be as complex as you want. I want to get started by analysing our freeware phdcc.CodeModule module which is available at dnn.phdcc.com and contains the full source. This module makes it easier to add your own functionality without having to write a whole new module. Your code has access to the database and the ASP.NET and DNN APIs.

phdcc.CodeModule simply renders your own user control. Once the module is installed, write your own code, eg in file intro.ascx. Upload the file to the DesktopModules/phdcc.CodeModule/ directory. Add an instance of the phdcc.CodeModule module to the desired DNN page and configure it to use intro.ascx. intro.ascx can contain normal HTML, ASP.NET code and DNN calls - phdcc.CodeModule will display this whenever requested.

phdcc.CodeModule's initial display:

Enter your control filename in the Settings:
Suppose intro.ascx contains this HTML and code to say hello to the current user:
<%@ Control Language="VB" ClassName="intro" %>
<%@ Import Namespace="DotNetNuke.Entities.Users" %>
Hello <% =UserController.GetCurrentUserInfo.Username%>

Then this is the output, within the SimpleContainer that we made earlier:
As stated earlier, you can switch off the container header so it simply says "Hello host". The code simply says "Hello" if no user is logged in.

Development environment

To develop for DNN, download the latest Starter kit vsi file for Visual Web Developer Express or Visual Studio 2005. Double click on this and the Visual Studio Content Installer creates a few DNN-related templates. To create a whole new DNN site, select [File][New][Web site...], "Visual Basic" and the "DotNetNuke Web Application Framework" template. After the site has been created, an instructions page is shown. You can simply compile and run if you have SQL Server 2005 Express installed. But wait...

To use the full version of SQL Server, create an empty new database, typically with a SQL Server owner login, and put the connection string in Web.Config in two places as instructed. I also set the only instance of "objectQualifier" to eg "DNN_" so that all DNN database tables etc have this prefix. I also usually increase authentication+forms-timeout from 60 (seconds) to let users stay logged in for longer, also setting slidingExpiration to "true". The last task that I might do before running is to convert from Visual Studio's built-in web server to use the local IIS - using an IIS virtual directory seems to work OK. This lets me test how the site looks in FireFox etc.

I then compile and run. With all the above already set up, you can select the "Auto" install option instead of the "Typical" wizard. If installed using "Auto", your first task will be to login as host and admin to change the passwords. Then go to [Host][Host Settings] eg to set the SMTP Server, and then set up the site/portal using [Admin][Site Settings]. Go back to Home and delete all the module instances - then off you go.

The created project has the 'code' half of DNN supplied as DLLs in the /bin/ directory. The web pages, controls, their code-behind and other resources are installed as web site source. All this lot can take a while to compile, which can be a bit frustrating when working on your own module - I did attempt once to convert DNN to a faster Web Project but I couldn't get it to work.

phdcc.CodeModule

Use [Host][Module Definitions] then "Install New Module". Browse to find phdccCodeModule_01_00_02.zip and click "Install New Module". Check the installation log for errors then press "Return". The following files will have been installed under the existing root DesktopModules directory.
DesktopModules
+ phdcc.CodeModule
+ App_LocalResources
+ Edit.ascx.resx
+ Settings.ascx.resx
+ View.ascx.resx
Edit.ascx, Edit.ascx.vb
Settings.ascx, Settings.ascx.vb
View.ascx, View.ascx.vb

The module zip also contains a manifest file phdcc.CodeModule.dnn that tells DNN where to install each file - in addition, it says that there are three controls: a View, Edit and Settings.

The View control displays the normal module output to users. The Settings control is at the bottom of the module settings page - visible to admin users. The Edit control is not used by phdcc.CodeModule yet but will typically be shown to admin users to set up the module content. You can have more user controls if need be.

View

View.ascx primarily contains the following placeholder into which your user control will be inserted. The view contains additional elements which are used to give instructions and show errors.
<asp:PlaceHolder ID="ViewControl" runat="server" />

The View.ascx.vb codebehind class is based on DotNetNuke.Entities.Modules.PortalModuleBase which is in turn derived from DotNetNuke.Framework.UserControlBase and eventually System.Web.UI.UserControl. These base classes contain many crucial DNN properties and objects, ranging from the PortalId to the current UserId. The View Page_Load uses PortalModuleBase.Settings to retrieve your control filename before adding it to the ViewControl PlaceHolder:

Dim ControlName = CStr(Settings("control"))
ViewControl.Controls.Add(LoadControl(ControlName))


If you have not specified your control yet, the code shows an asp:label and a "Settings" asp:button. If this button is clicked then you are redirected to the module's Settings - the DNN NavigateURL global method is used to create the correct URL to see the Settings. In DNN parlance, each page is called a "tab" so the TabID selects the page; there may be more than one module on a page, so the correct one is selected using the ModuleId:
Response.Redirect(DotNetNuke.Common.Globals.NavigateURL(PortalSettings.ActiveTab.TabID, "Module", "moduleid", ModuleId.ToString()), False)

phdcc.CodeModule could be improved by adding an "Edit Control" option to the module menu (to let an admin user create or edit the control in a text area edit box). The existing View implements the IActionable interface to do this job. The ModuleActions get property must return a DNN ModuleActionCollection with a new Action added using localised text. DNN sets up this action so that selecting it redirects to the Edit control.

Settings

The module settings page has a common user interface at the top of the page - our content is shown lower down, as shown above. The Settings.ascx user control primarily shows a labelled text box for you to type in your control filename.
<asp:textbox id="txtControlName" cssclass="NormalTextBox" width="200" runat="server" />

The codebehind Settings class is derived from DotNetNuke.Entities.Modules.ModuleSettingsBase. The LoadSettings method fills the text box with the current setting on first entry. ModuleSettingsBase.TabModuleSettings gets the same information that is retrieved by the view using PortalModuleBase.Settings. Please see my blog on DNN module and tab-module settings for full details of the different settings available.

If Not Page.IsPostBack Then
Dim ControlName = TabModuleSettings("control")
txtControlName.Text = ControlName
End If


When the Settings page Update button is pressed, the UpdateSettings method is called to store the new control filename and refresh the DNN in-memory cache:

Dim objModules As New DotNetNuke.Entities.Modules.ModuleController
objModules.UpdateTabModuleSetting(TabModuleId, "control", txtControlName.Text)
SynchronizeModule()


Localisation

The only remaining element of phdcc.CodeModule is the localisation files in the App_LocalResources directory. In this module, only the Settings.ascx.resx file is used. In Settings.ascx, the dnn:label control is used for the filename prompt:
<dnn:label id="lblControlName" runat="server" controlname="txtControlName" suffix=":">

This shows a help question mark followed by a label, with the actual text picked up from the resource file "lblControlName.Text" and "lblControlName.Help" strings. Within DNN you can edit these strings or create a new resource file for a different locale. DNN looks at the logged-in user's UserInfo.Profile.PreferredLocale property to determine which resource to use.

Tip 1

We recently wrote a simple phdcc.CodeModule user control that would not save its ViewState, eg:
<% If Not IsPostBack Then lblGiven.Text = "init" %>
<asp:Label ID="lblGiven" runat="server" />
If a button was pressed then lblGiven would not contain "init" on postback. The problem turned out to be that embedded code blocks <% %> are run in the PreRender phase, after the ViewState had been saved. The solution was to set the label in the Page_Load method.

Tip 2

In a phdcc.CodeModule control, it is useful to have access to the current DNN PortalModuleBase instance. The following code achieves this:

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim ViewControl As Control = Me.TemplateControl.Parent
Dim View As Control = ViewControl.Parent
If TypeOf (View) Is PortalModuleBase Then
Dim pmb As PortalModuleBase = CType(View, PortalModuleBase)
Dim ps As PortalSettings = pmb.PortalSettings
End If
End Sub


Tip 3

Sometimes it is not clear whether to use DNN or ASP.NET functions. If in doubt, rummage around in the DNN source to see how they do it. For example, to determine if the user is logged in, I use ASP.NET Request.IsAuthenticated. In another case, I wanted to get someone's password given their UserId. After getting the Username using DNN APIs, I used the ASP.NET GetUser method to return a MembershipUser that is used to get the password.

UserInfo ui = UserController.GetUser( PortalId, UserId, true);
MembershipUser user = Membership.GetUser(ui.Username);
string pwd = user.GetPassword();


Database Modules

More substantial modules will typically want to store their own information in the database. The following example module shows a list of Text/HTML items (in an asp:datalist data bound list). There are admin options to Add Content items and define a template in Settings.

In Visual Studio, right click on the project name and select [Add New Item...], choose "DotNetNuke Dynamic Module" for VB or C#, enter a name such as "TestMod", then press OK. You will have to do two directory renames as per the shown instructions. If you chose C# then you will have to add to the main Web.Config codeSubDirectories element.

In [Host][Module Definitions] click on "Import Module Definition". In the Manifest list, select "TestMod.dnn" and click "Import Manifest". The TestMod module can now be put on a page but will show a ModuleLoadException until the database has been set up.

The new module will have code classes created in the \App_Code\TestMod\ directory. File TestModController.vb represents your business logic and provides access to the database from your user controls. The Text/HTML items are each represented by a TestModInfo.vb object, each of which is stored as a row in a database table. File DataProvider.vb describes a generic database interface, while SqlDataProvider.vb proves an actual implementation of this interface.

Controller

The generated TestModController class has a GetTestMods method that returns a list of TestModInfo objects. It finds the current data provider and calls its GetTestMods method, passing the current module instance id ModuleId. The data provider GetTestMods returns an IDataReader. The DNN Custom Business Object utility class CBO method FillCollection is used to create the TestModInfo list - it sees what fields have been returned in each IDataReader record and fills the corresponding properties in each TestModInfo instance automatically - quite a mouthful but certainly a useful tool. There is a similar method CBO.FillObject to fill just one object.

Public Function GetTestMods(ByVal ModuleId As Integer) As List(Of TestModInfo)
Return CBO.FillCollection(Of TestModInfo)(DataProvider.Instance().GetTestMods(ModuleId))
End Function

The controller also contains further methods GetTestMod, AddTestMod and UpdateTestMod which work with a single TestModInfo object, using matching functionality in the data provider.

The controller class also implements the DNN ISearchable and IPortable interfaces, to support searching and import/export of the module content into XML.

Database SQL

When a module is installed, DNN looks at the manifest version number and compares it to certain filenames in the zip. File 01.00.00.SqlDataProvider contains the SQL instructions to create any tables, stored procedures etc required to bring the database up to version 01.00.00. A script 01.01.03.SqlDataProvider would take the database to version 01.01.03. When installing as new, then both scripts will be run. If you are upgrading from say version 01.00.01 then only the second one is run.

Because you have created a module from a template, the database has not have been created. To set this up, you need to run the 01.00.00.SqlDataProvider script in SQL Server Management Studio, though you may want to rename some of the tables etc first. You should also first replace {databaseOwner} with "dbo." and {objectQualifier} with the "objectQualifier" that you used, ie nothing by default.

The generated TestMod SQL script creates a table called YourCompany_TestMod (for TestModInfo objects) and various stored procedures that correspond to the data provider GetTestMods, GetTestMod, AddTestMod and UpdateTestMod methods. The YourCompany_TestMod table contains a ModuleId field that corresponds to the unique module identifier with the same name in the DNN Modules table.

When you add a TestMod module instance to a page there is no event raised to let you populate the database. The View code knows its ModuleId, so it calls GetTestMods to obtain all TestModInfo objects for the current module instance. If it finds that there are no such objects, then it creates a default TestModInfo and adds it to the database.
Dim objTestMods As New TestModController
Dim colTestMods As List(Of TestModInfo)
colTestMods = objTestMods.GetTestMods(ModuleId)
If colTestMods.Count = 0 Then
Dim objTestMod As TestModInfo = New TestModInfo
objTestMods.AddTestMod(objTestMod)
colTestMods = objTestMods.GetTestMods(ModuleId)
End If

There is no event raised when a module is deleted from a page. So when is your per-module-instance data deleted? DNN has a Recycle Bin for pages and modules that have been deleted. When a module is finally deleted from this bin, the module instance row in the Modules tables is deleted - our YourCompany_TestMod table has a cascade delete relation on this row, thereby deleting the right rows in our table. Unfortunately, there is no programmer event that corresponds to this final delete.

Finally, the generated module also contains another SQL script file Uninstall.SqlDataProvider that is run if the module is uninstalled from the system in [Host][Module Definitions..]. The SQL in there naturally deletes all tables etc that were created in the database.

I will leave an exploration of the rest of the code as an exercise for you dear reader. There are no imposed limitations on how you use the database, so you can create whatever tables you want. Your code has access to all DNN and ASP.NET data, not to mention bog standard Session variables etc, so the world is your oyster.

And more...
If you develop a module and want to keep your source private then follow the documentation instructions. Another option is to make the App_Code section of your module into a separate DLL class library project (while keeping the web site controls with full source). Include the project DLL in your module's ZIP so that it installs on other systems in the /bin/ directory.

Module code also has full access to the web site file system. It is common to store files relating to a site in the relevant portal directory, eg /Portals/0/. It would be sensible if each module worked within a suitably named sub-directory.

There is stack of DNN functionality and namespaces which I haven't touched upon. However I hope that I have given you a flavour of what DNN is and how to extend it with your code – in a simple way using phdcc.CodeModule, or by writing a full-blown custom module.

Writing DNN Skins

This is the second of three blog entries that make up a full introduction to DotNetNuke (DNN) from a developer's point of view. This time I cover writing a skin and a container to change the appearance of a DNN web site. Last time, I gave an Introduction to DNN - next will be Writing Custom Modules. A concise version of the complete article first appeared on 1 July 2008 in UK programming magazine VSJ.

The simple skin and container used in this blog can be downloaded here.

DNN5 has a new extension/package structure for custom modules and skins - however 'legacy' DNN3 and DNN4 skins will install in DNN 5 - select Skin as the legacy skin type.

Overview

This time, I look at how to create a skin that defines the appearance of a site and write custom modules that provide content on a page. The figure below shows a typical DNN layout: the skin defines the page layout, including one or more panes. An admin user can place one or more modules in a pane, with each module wrapped in a container.

Skins

A skin package defines how DNN presents its content, ie the overall HTML of the page, all the CSS styles, and various parameters for the breadcrumb and menu screen elements. A container package defines how each module container looks in a similar way. If you install a ‘skin’ it typically contains both skin and container packages. An individual skin package may contain several different layouts, eg fixed width and full-screen instances.

DNN seems to have a preference for fixed-width skins and fixed size fonts. I presume that this is an attempt to achieve a perfect appearance on the page. However, it is best to make use of all the screen width and make the page work well as font sizes are changed by the user. Fixed sizes may achieve the desired effect in Internet Explorer, as long as the user doesn't Zoom. However in FireFox, changing the text size also changes fixed point sizes in the same way as IE Zoom. For more details, see my previous blog entries: DotNetNuke Internet Explorer fixed size fonts and DotNetNuke Internet Explorer font scaling.

These CSS files are used by DNN, in this order, with the Portal CSS included last so its definitions take precedence over any earlier ones.

  1. Portal default CSS (in /portals/_default/default.css)
  2. Current skin CSS
  3. Current container CSS
  4. Portal CSS (eg in /Portals/0/portal.css)
The ‘portal default CSS’ defines styles for most generic HTML elements (H1, TD etc) though strangely not P – using fixed-point font sizes in most cases; it also defines CSS classes for all the standard DNN elements.

The ‘portal CSS file’ contains blank elements to let you know what the standard definitions are – this file can be updated in the [Admin][Site Settings] menu.

Skin and container development

If you have installed the DNN Starter Kit into Visual Studio, you can create a skin and container using [File][New][File] - "DotNetNuke Skin" template. However, to illustrate a basic skin and container, I have developed a simple example which you can download. These are the files that I used to create the example:
skins/simpleskin.htm
skins/skin.css
skins/skin.xml
skins.zip

containers/simplecontainer.htm
containers/container.css
containers/container.xml
containers.zip

simple.zip

The files in the skins directory are zipped up to make skins.zip, ditto for containers. Then these two are zipped together to make simple.zip that contains both the skin and container packages.

Each site has one current skin and one current container. When you install a skin, the files are unzipped and a skin user control is created - when chosen as the current skin, this user control is inserted into the main page output. A skin has pane elements - DNN inserts the right modules into each pane, each wrapped in a container. The current container is also a user control with a ContentPane that contains the actual module output.

If you install a skin from the [Admin] menu, then the packages will be installed in directory \portals\0\skins\simple\ and \portals\0\containers\simple\. The file simpleskin.htm contains various template tokens, such as [LOGO], that are expanded on installation into file simpleskin.ascx – the ASP.NET user control that is included in the DNN output. If you update the in-situ simpleskin.htm, then click on "Parse Skin Package" in [Admin][Skins] to recreate simpleskin.ascx to see your changes. (Don't include any ascx files in your final skin/container zip package.)

The SimpleSkin.htm skin template defines the basic HTML of the main DNN output, typically using tables for layout. Various tokens appear in square brackets, replaced by DNN at install/runtime. The main output panes are defined in this example as table cells with the predefined correct id names. If you are better at XHTML than I am, change this template to avoid tables.

<table class="ss_header" width="100%">
<tr>
<td>[LOGO]</td>
</tr>
</table>

<table class="ss_menurow" width="100%">
<tr>
<td class="ss_menu">[SOLPARTMENU]</td>
<td class="ss_menu_user">[USER] [LOGIN]</td>
</tr>
</table>

<table class="ss_main" width="100%">
<tr>
<td class="ss_crumb">[BREADCRUMB]</td>
<td class="ss_date">[CURRENTDATE]</td>
</tr>
</table>

<table class="ss_content" width="100%">
<tr>
<td id="LeftPane" runat="server" />
<td id="ContentPane" runat="server" />
</tr>
</table>

The skin.css file contains all the definitions you want (not forgetting to override the DNN defaults if required), eg simply:
.ss_date,.ss_menu_user
{
text-align:right;
}
The skin.xml file defines parameters that are used by DNN. The first one shown tells DNN to use the » character as a separator between the breadcrumb page names. The second set of parameters makes the menu horizontal, etc.
<Objects>
<Object>
<Token>[BREADCRUMB]</Token>
<Settings>
<Setting>
<Name>Separator</Name>
<Value><![CDATA[ » ]]></Value>
</Setting>
</Settings>
</Object>
<Object>
<Token>[SOLPARTMENU]</Token>
<Settings>
<Setting>
<Name>display</Name>
<Value>horizontal</Value>
</Setting>
...
The SkinContainer.htm container template looks similar to the skin template, with tokens replaced when the container is rendered. The main module output is in the ContentPane. The container.css file contains definitions for the CSS classes used in the container template. Similarly, container.xml defines various container-level parameters.
<table width="100%" class="c_all">

<tr>
<td class="c_header" colspan="2">
<table width="100%">
<tr>
<td>[SOLPARTACTIONS]</td>
<td>[ICON]</td>
<td style="width:100%;" class="c_header_title">[TITLE]</td>
</tr>
</table>
</td>
</tr>

<tr>
<td class="c_content" id="ContentPane" runat="server" colspan="2" />
</tr>

<tr>
<td>[ACTIONBUTTON:1]</td>
<td>[ACTIONBUTTON:2]</td>
</tr>

</table>
There are many more tokens and options that you can use. Ideally you should also include a preview of your skin and container by including in each zip a JPG with same name as your template file.

02 September 2008

DNN Introduction

This is the first of three blog entries that make up a full introduction to DotNetNuke (DNN) from a developer's point of view, with the following pieces covering Skins and then Writing Custom Modules. A concise version of the complete article first appeared on 1 July 2008 in UK programming magazine VSJ.

Overview

DotNetNuke is not a good name for software, but don't let that put you off. Call it DNN, and think of it as a programmable content management system that's open source and runs on ASP.NET 2+ servers. Ie it lets you create and maintain a web site easily.

Although you may be familiar with the basic idea of a CMS, this first article will introduce you to DNN features, terminology and usage before getting stuck into new skins and programmable modules next time.

If you want to use Microsoft technology, DNN lets you deliver a working site with relative ease - it can be handed over to a non-technical site administrator who can do all the regular site updating, eg adding pages, editing text, making announcements etc. Web space at our preferred host Crystaltech starts at US$35 per year for a small site. At that price everyone can have a web site: I set up a badminton club site for a friend and he's now running it with no intervention from me.

There are some problems running DNN in Windows Server 2008 in Medium Trust.

When trying to decide between various systems, you will want to know:
  • Is it reliable? We are now running a number of DNN sites with many thousands of users and they seem solid.
  • Is it maintained? There are regular fixes and improvements for DNN, eg it now uses AJAX in places.
  • Does it have the features you want? DNN handles users well, has a good framework to add functionality, and there are lots of add-on modules available.
  • Can I learn it easily? It will always take a while to get in-depth understanding of a new system. With DNN you can set up a basic site on day one, but it might take months to get fully to grips with writing modules. DNN was a bit easier for us as we are used to ASP, ASP.NET and Microsoft hosting. To handle development, staging, production and backup servers, make sure you are able to copy all content and users to another site.
DNN is not perfect. The Text/HTML Editor is not good enough for serious use. Cute Editor and other plug-in editors may be better. They all suffer from not being clever enough at handling complex reformatting - what you see is seldom what you get. Note that users must have cookies enabled to be able to register and login at DNN.

Extensibility

Supplied Modules
Account login
Banner adverts
Feed explorer
Google Adsense
Links
Search Input
Search Results
Text/HTML
User Account
Announcements
Blog
Documents
Events
FAQs
Feedback
Forum
Help
IFrame
Media
NewsFeeds
Reports
Repository
Store
Survey
UserDefinedTable
UsersOnline
Wiki
XML

DNN serves most content using modules, eg a text module comes as standard, along with the others listed below. There's a thriving third-party market for modules, some for free but largely at low cost - usually listed and bought at SnowCovered.com. At phdcc, it is crucial that we can develop custom modules to provide the unique functionality that some web sites need. We have also released some freeware and commercial modules. Custom modules are written as a set of standard ASP.NET user controls, using extra DNN APIs as needed, with the end result bundled into a ZIP for distribution. Custom modules are usually written in Visual Basic like DNN itself, but C# is possible.

If you like to hedge your bets, code in a DNN module could be written to run on a plain ASP.NET system. Or perhaps do it the other way round: for example, the YetAnotherForum.net open source software will run as a standalone web app, but they have added some code and packaged it as a DNN module.

DNN's presentation is determined by skins which define the appearance of the whole site and each module container. Various skins are provided for free, while others are available for purchase at SnowCovered - you can write your own relatively easily. DNN skins tend to use fixed point size CSS definitions so it doesn't scale in Internet Explorer as you change text size - more on this next time and in these previous blog entries: DotNetNuke Internet Explorer fixed size fonts and DotNetNuke Internet Explorer font scaling.

Technology

It is worth saying right now that DNN is almost entirely database and ASP.NET driven. All DNN output is generated from a single ASP.NET page that dynamically loads whatever content needs to be shown, using many levels of ASP.NET .ascx user controls. So the site will not be as fast as from static content, but this is an acceptable price.

Although all DNN requests are run through the root default.aspx, it has a friendly-URL HttpModule that shows a unique URL with pertinent text for each page. Note that the standard menu system is implemented in JavaScript so it does not provide hard links. So make sure that you provide hard links somewhere so that search engines can trundle through your site.

The database can be in SQL Server Express or the full SQL Server. Strictly speaking, the code has been written in a generic way using a provider pattern that allows other database types to be used, but in practice I have only ever seen the Microsoft databases supported.

Doing backups in DNN can be complicated. There are facilities to export and import your site as a template in XML - but I am not confident that this gets a whole site design – and does not store the users. Doing a database backup will probably get most data. However to be certain, you will also need to backup the entire file system as some modules store their data in files.

DNN uses standard ASP.NET forms authentication to handle its users (recently, Windows LiveID as well). After a fresh install, the 'host' account lets you configure the entire site, while the 'admin' account has a reduced set of permissions that is suitable to give to a non-technical site administrator. One "host" DNN instance can have one or more "portals" (one for each site/domain name), so there is one admin account for each portal. We wouldn't recommend having more than one portal - it's not worth the fuss when web space is so cheap. Things go wrong if you attempt to run DNN from a sub-directory, so always make sure that it runs at the root of a domain name - one approach is to set up a sub-domain such as dnn.phdcc.com. On our shared host, we did this by adding an extra DNS Host Record (A) for "dnn" that points to the IP address of a separate hosted web space.

DNN has built-in support for internationalisation, so you can download language packs for DNN itself so that all DNN management will be done in your preferred language, including email templates. Well written modules will use the same techniques but will usually only have English text provided. As I understand it, there is no direct support for showing a particular module (eg French intro text) if the user's browser setting or preferred locale is French.

It is helpful to understand a little of the DNN directory structure. As well as /bin/ and /App_Code/ directories, each supported site has a directory, so the first site portal (with a programmer PortalID of zero) has its file store at /Portals/0/. [Admin][File Manager] opens at this directory. If, with your super powers, you FTP a new file into this directory, then some of the DNN code may not initially see it. There is an option in File Manager to synchronize with the actual files present - don't forget to tick the Recursive box to sync all the sub-directories. You can also synchronise programmatically using the DNN FileSystemUtils.Synchronize method. Finally, the directory /Portals/_default/ contains definitions that are available for all portal sites, eg skins and containers.

DNN has a basic search built in, using the search information provided by each module. However the search will not index file documents such as PDFs.

Downloads

You can get the latest version of DNN, now 4.8.4, from http://www.dotnetnuke.com/ once you have registered. The "Install" package is for production use. Get the "Starter Kit" and "Documentation" packages if you are doing module development - there is a full "Source" release as well. For installation, your primary requirement is a connection string for a database, and you will need to make sure that DNN ASP.NET has full read, write and delete access to the file system. An installation wizard will take you through these options and set up the host and admin accounts. There's also an "Upgrade" package - but make sure that you have done a full backup before using this. When upgrading, you must carefully merge the existing DNN Web.Config file with the new one supplied in Release.Config, in particular keeping the existing ‘machineKey’ element, ‘SiteSqlServer’ connection strings and ‘objectQualifier’ value.

I have one of the standard books, 'Professional DotNetNuke ASP.NET Portals' for old DNN 3. This aims to cover setting up and administering a site, as well as writing skins and modules. I didn't find it very helpful and it is hopeless as a programmer reference as the index does not list the things I looked for. The "Documentation" download has various useful documents, but I seem to have missed a trick somewhere as I haven't found or generated a full DNN API help file. That said, it is great having the full DNN source available so you can look up methods and see how they work.

I haven't ever wanted to alter the main DNN code which is just as well if you want to track upgrades. There's a large developer community at work in the DNN help forums. I made one paltry contribution to sorting out one bug. I have however updated the wiki module to send me an email whenever a wiki entry is updated; I was pleased to be able to compile the wiki project, understand the code, and make my changes with relative ease.

Using DNN

Here's a simple overview of how to use DNN, without endless screen shots. A DNN page is typically laid out as shown below. The current skin defines various panes on the screen where you can place one or more modules. After you have added a module, it appears in a container, a rectangle with a title bar in a style determined by the container skin. DNN is generally very configurable, eg you can switch off the title bar for each container.
When logged in as 'host' or 'admin' you get a Control Panel at the top of the page as shown below - most functions are pretty obvious.
DNN supports various roles that determine which users can do what. Roles can be assigned to the view and edit visibility permissions for each page and module; the File Manager can set up view and write permissions for portal directories. Users who have the "Administrators" role can access everything. The other standard roles are 'All Users', 'Registered Users', 'Subscribers' and 'Unauthenticated Users'. You can set up new roles, for example, a fee-paying role that might be used to allow access to privileged content for say 3 months. DNN has built in support for several payment providers, including PayPal.

Below is an amalgamated screen shot that shows the menu options available to admin and all-powerful host users. Below is a typical Text/HTML module shown with its container title - and a module menu visible to admin users with edit permissions. When you write a module, you can add functionality to the module Settings page, and also add items to the menu itself - in the same way as "Edit Text" as shown.

DNN maintains a profile for each registered user, ie a set of properties of various types. As well as the basic profile properties of Username, FirstName, LastName, DisplayName and Email, DNN has many default properties for your Address, Contact Info, Biography, Time Zone and Preferred Locale. You can delete or add properties and mark individual properties as being required if desired. At program level, each user has a unique UserID; if a user joins with Username 'chris' then unregisters and subsequently someone else joins with the same Username 'chris', then these two users will have different UserIDs.
DNN has a rather fixed user interface for registration and setting your profile. For this reason, our phdcc.Data module has a form generator that lets you build custom alternatives to these facilities.

In conclusion, DNN provides a useful set of CMS/multi-user facilities for Microsoft-technology web sites. Any limitations can often be overcome using one of the many modules available. Everything DNN outputs is code and database driven which makes it harder to backup and understand what is going on. Next time, we'll look in detail at how to set up new skins and program modules.

08 July 2008

Simple DNN skin download for my VSJ article

An article I have written about DotNetNuke (DNN) has just appeared in print in July/August 2008 issue of Visual Systems Journal (VSJ). The article appears in my blog in three entries: DNN Introduction, Writing DNN Skins and Writing DNN custom modules.

Please read my blog article DNN module and tab-module settings for an article update on the different settings available in DNN module development.

The simple skin and container can be downloaded here.

The phdcc.CodeModule module can be downloaded from here.

19 May 2008

DNN module and tab-module settings

If you write a custom DotNetNuke (DNN) module, be careful with your use of the two types of settings that can be associated with an instance of a custom module - there are Module settings and Tab-Module settings. The difference becomes important (a) when you use Add Existing Module on another page and (b) when you Export Content from a module. In general, it makes sense to use the Module settings.

The Module settings are associated with all instances of a module on several pages, while the Tab-Module settings are only associated with one instance (on a single page).

When you copy a module instance to a new page with Add Existing Module, only the Module settings are available on the new page. So, if you want settings to be available after a module has been copied, then use Module settings, not Tab-Module settings.

When a module's content is exported using Export Content, the module controller is called with the ModuleId only (ie without the TabModuleId). This only provides easy access to the Module settings.

Implementation:

Most of the following implementation examples require the use of the DNN ModuleController, obtained as follows in C#:
ModuleController objModules = new ModuleController();

Most custom modules have a Settings user control that appears as a part of the module's main Settings page. When the user clicks on Update, the module setting's UpdateSettings() method is called. To save a Module setting, eg for a CssFile text box, use the following:
objModules.UpdateModuleSetting(ModuleId, "CssFile", txtCssFile.Text);
If instead, you want to save a Tab-Module setting, use the following:
objModules.UpdateTabModuleSetting(TabModuleId, "CssFile", txtCssFile.Text);

The Settings user control is initialised using its LoadSettings() method. The ModuleSettingsBase Settings[] property provides access to the current settings, eg:
txtCssClass.Text = Settings["CssClass"] as string;
Settings[] contains both the Module and Tab-Module settings, with the Tab-Module settings taking precedence.

Within your View control, the PortalModuleBase Settings[] property is the same.

Other methods available are objModules.DeleteModuleSetting() and objModules.DeleteTabModuleSetting().

To implement Export Content, your module controller must implement IPortable. You must then write an ExportContent() method that might start as follows:
public string ExportModule(int ModuleID)
{
ModuleController objModules = new ModuleController();
Hashtable Settings = objModules.GetModuleSettings(ModuleID);
string CssFile = Settings["CssFile"] as string;


Several of my existing modules used Tab-Module settings. I have converted them to use Module settings. I was careful to transfer any existing settings from Tab-Module to Module. I used a "SettingsInModule" module setting set to "done" to indicate that the settings had been transferred. My conversion method was careful to work for brand new modules where no settings exist at all.

28 April 2008

DotNetNuke Internet Explorer font scaling

Updated May 2009 to cope with MinimalEntropy skin.

When you put content onto a DotNetNuke (DNN) site you must think how other users see your information. By default, if your visitors use Internet Explorer (IE) then most of your site will NOT resize according to the user's Text Size setting. In my opinion this is not good practice because some users prefer larger text than others. This post shows how to make DNN fonts scale correctly. An earlier post shows what happens if you do not make my changes.

In this post I make the changes in a portal.css stylesheet file - click here to download DNN_ScalingFont_Portal_090511.css. You may use and update this file freely provided you retain the attribution to Chris Cant. Note that this CSS file only affects the fonts shown to ordinary users - Edit and Admin fonts are unchanged.

To use my CSS file, log in as an Administrator or Host then choose [Admin][Site Settings]. Scroll down and open up the [Stylesheet Editor]. Select all the existing text, copy and paste to a local text file as a backup. Insert my CSS file at the start of the style sheet textarea, then click on [Save Style Sheet]. To see the effect of the new file, you will have to navigate to a new page (eg Home) and refresh your browser (F5).

Note that my CSS stylesheet is not guaranteed to make all fonts scale on your site - the current skin and container may have definitions that take precedence over the portal CSS. Please test this stylesheet carefully on all pages of your site before putting into production use.

How it works

DNN uses several CSS stylesheets when rendering content. The following stylesheets are included in this order:
  1. Portal default CSS (in /portals/_default/default.css)
  2. Current skin CSS
  3. Current container CSS
  4. Portal CSS (eg in /Portals/0/portal.css)
The portal default CSS has fixed-size definitions for many generic HTML elements as well as many DNN-specific classes. My substitute Portal CSS overrides these definitions. Because the portal CSS is included last, it has a good chance of having its definitions used, though some skins and containers may have specific definitions that take precedence.

The DNN Portal default CSS uses fixed size fonts, eg H1 has font-size:20px. This is fixed in my CSS by specifying the font size as font-size:x-large which lets IE resize the font as the user changes Text Size.

Font Family Definitions

The first section of my CSS gathers all the font family definitions into one place. By altering the definitions here, you should change all the font families that are normally visible. The normal fonts are defined here as being Tahoma, Arial, Helvetica; the BLOCKQUOTE, PRE and CODE elements are Lucida Console, monospace. Alter all instances of both of these as you wish.

Scaling fonts and default weights/colours

The next section of my CSS file does the main work of redefining generic HTML tags and DNN classes. The general HTML elements are put back to standard font size, colour and weight. Firefox (FF) and IE have different (but similar) defaults - the IE default is used.

Next, various DNN classes are defined to use scaling font sizes.

Main menu and Module container menu scaling

The main menu and module container menu are now made scaling. The main menu bar by default has a fixed size of 16px. This is changed to have a scaling size of 2em. The font sizes of the menu classes are defined as 1em. A similar job is done for the module container menu classes.

Other classes

The existing style sheet has definitions for various classes used by the standard DNN template. In many cases you can clear these definitions.

Example sites

25 April 2008

DotNetNuke Internet Explorer fixed size fonts

DotNetNuke (DNN) by default uses many fixed size fonts in its standard CSS styles. I show the effect of these fixed font sizes in Windows Internet Explorer (IE) as you change the text size from Smallest to Largest. Within Text/HTML modules, everything is fixed size except text within TD elements.

DNN uses several CSS stylesheets when rendering content. The following stylesheets are included in this order:
  1. Portal default CSS (in /portals/_default/default.css)
  2. Current skin CSS
  3. Current container CSS
  4. Portal CSS (eg in /Portals/0/portal.css)
The Portal default CSS file has definitions for many generic HTML elements (as well as defining many DNN specific CSS classes). For example, tag H1 is defined as having font-size: 20px;

DNN defines fixed font sizes for these generic HTML elements:
H1, H2, H3, H4, H5, H6, DT, TFOOT, THEAD, TH, SMALL, BIG

There are two cases that I would like to highlight: (a) writing HTML within a Text/HTML module and (b) writing HTML within a custom module or in a skin/container.

In the following examples, you will need to remember that the module output is encased first a module container and (outside that) the skin. In the test environment, both the skin and container ContentPane elements are TD elements.

IE Scaling in a Text/HTML module

When you place a Text/HTML module on a page, the module always surrounds the entire text in a DIV element that has an attribute class="Normal". The Normal class is defined in the default CSS as having a fixed point size (font-size: 11px;).

The following image shows two screenshots that let you compare how Internet Explorer displays various tags with the Smallest font size on the left and the Largest font size on the right. As you can see, the only text that scales is within a TD tag.

It is important to understand what is happening for the other elements. The P tag for instance does not have a definition in the default CSS. However it inherits its font size from its surrounding DIV tag which has class "Normal" that has a fixed size. For some reason, the TD tag does not inherit this font size.

Note that other tags in the example have fixed font size definitions in the default CSS, eg H1 is defined as having font-size: 20px;


IE Scaling in a custom module or skin/container

The following screenshot shows the same HTML - this time in a custom module (not Text/HTML). The output will be the same if you place the HTML within a skin or container.

In this case, the HTML is NOT surrounded by a DIV element and so the HTML does not inherit the Normal class. This time, the P, TD, DIV, DD, BLOCKQUOTE and PRE tags scale as you change the IE font size. This is because all these elements do not have a (fixed size) definition within the DNN default CSS. All the other elements stay the same size because they do have fixed size CSS definitions.


Conclusion

When you put content onto a DNN page you must think how other users see your information. If your visitors use Internet Explorer then most of your site will NOT resize according to the user's Text Size setting. In my opinion this is not the recommended practice because some users prefer larger text than others - more on how to make all your text resize in my next blog entry.

The Firefox Text Size and Internet Explorer Zoom Level options DO alter the size of fixed size fonts.

To achieve a consistent fixed size effect you need to provide a font size definition for any TD elements you use within Text/HTML modules. For text in other modules or skins, you need to think carefully about which elements scale and which do not.

To change the sizes of the fonts used for various elements, you can alter any of the CSS files mentioned at the top of this post - typically it will be easiest to alter your Post CSS file.

09 March 2008

ViewState in ASP.NET Embedded Code Blocks

This morning I had trouble getting an ASP.NET user control to access its ViewState on postback. The crucial trick was to populate control items in a Page_Load function not in an embedded code block. The reason is because the code block is only run at render time which happens after the ViewState has been saved.

(I had been loading the user control dynamically using LoadControl, but that turned out to be not relevant.)

Now, within a user control, suppose I have a wee label and an ineffectual button:
<asp:label id="saver" runat="server" />
<asp:button id="Button1" runat="server" text="Go" />


If the label is set using an embedded code block
<% if (!IsPostBack) saver.Text = "Am I saved"; %>
then it is not preserved/shown after the "Go" button has been pressed.

However, if it is set in Page_Load then the label text is shown correctly after "Go" is pressed.
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
saver.Text = "Am I saved";
}