DotNetNuke Error after Uploading New Skin

March 1, 2013

After uploading a new skin I developed onto a DNN 6.1.4 installation, the Host/Extensions page did not display the list of installed modules, skins, etc. anymore, but only showed the error message

Object reference not set to an instance of an object.

The current log file under \Portals\_default\Logs contained the complete stacktrace:

[date:time] [server][Thread:14][FATAL] DotNetNuke.Framework.PageBase - An error has occurred while loading page.
System.NullReferenceException: Object reference not set to an instance of an object.
   at DotNetNuke.Services.Installer.Packages.PackageController.CanDeletePackage(PackageInfo package, PortalSettings portalSettings)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.extensionsGrid_ItemDataBound(Object sender, DataGridItemEventArgs e)
   at System.Web.UI.WebControls.DataGrid.CreateItem(Int32 itemIndex, Int32 dataSourceIndex, ListItemType itemType, Boolean dataBind, Object dataItem, DataGridColumn[] columns, TableRowCollection rows, PagedDataSource pagedDataSource)
   at System.Web.UI.WebControls.DataGrid.CreateControlHierarchy(Boolean useDataSource)
   at System.Web.UI.WebControls.BaseDataList.OnDataBinding(EventArgs e)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.BindGrid(String packageType, DataGrid grid, Label noResultsLabel)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.extensionTypeRepeater_ItemDataBound(Object sender, RepeaterItemEventArgs e)
   at System.Web.UI.WebControls.Repeater.CreateControlHierarchy(Boolean useDataSource)
   at System.Web.UI.WebControls.Repeater.OnDataBinding(EventArgs e)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.BindPackageTypes()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
[date:time] [server][Thread:14][ERROR] System.Web.UI.Page - ~/Default.aspx?tabid=36&error=Object+reference+not+set+to+an+instance+of+an+object.&content=0
System.NullReferenceException: Object reference not set to an instance of an object.
   at DotNetNuke.Services.Installer.Packages.PackageController.CanDeletePackage(PackageInfo package, PortalSettings portalSettings)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.extensionsGrid_ItemDataBound(Object sender, DataGridItemEventArgs e)
   at System.Web.UI.WebControls.DataGrid.CreateItem(Int32 itemIndex, Int32 dataSourceIndex, ListItemType itemType, Boolean dataBind, Object dataItem, DataGridColumn[] columns, TableRowCollection rows, PagedDataSource pagedDataSource)
   at System.Web.UI.WebControls.DataGrid.CreateControlHierarchy(Boolean useDataSource)
   at System.Web.UI.WebControls.BaseDataList.OnDataBinding(EventArgs e)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.BindGrid(String packageType, DataGrid grid, Label noResultsLabel)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.extensionTypeRepeater_ItemDataBound(Object sender, RepeaterItemEventArgs e)
   at System.Web.UI.WebControls.Repeater.CreateControlHierarchy(Boolean useDataSource)
   at System.Web.UI.WebControls.Repeater.OnDataBinding(EventArgs e)
   at DotNetNuke.Modules.Admin.Extensions.InstalledExtensions.BindPackageTypes()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

The DNN forums contain bug reports for this error message since at least 2008 (!), but most often the entries contain no solution on how to proceed. Or at least, on how to find the offending module, skin, control, whatever that causes the CanDeletePackage() function to fail.

You’d expect that during the last 4 years, somebody would have bothered to at least log the item that’s causing the error, or fix CanDeletePackage() to handle a NULL parameter. But no.

The only useful hint I found was on this self-answered question,

2 rows in the packages table were invalid- they were pointing at packages that no longer existed.  I think maybe i renamed or deleted these skins on the file system- guess i should not do that

So I had a look at the Packages table in the DNN database, and indeed it contained a record for a skin container that I did not declare. Probably that was caused by an empty

<components></components>

section in the .dnn file? I have no idea.

I deleted the record, and was able to load the Host/Extensions page again. I uploaded and updated the previously installed skin package, and things still worked. The erroneous record did not show up again.

Advertisements

Handling Exception in DotNetNuke Modules menu

June 27, 2012

For development, I use a couple of DNN 6 installations. Today I tried to import and use a newly created module, when the Modules menu displayed the following error text at the bottom:

An error has occurred.
DotNetNuke.Services.Exceptions.ModuleLoadException: An entry with the same key already exists. ---> System.ArgumentException: An entry with the same key already exists.
   at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.SortedList`2.Add(TKey key, TValue value)
   at DotNetNuke.Entities.Modules.DesktopModuleController.GetPortalDesktopModules(Int32 portalID)
   at DotNetNuke.Web.UI.WebControls.DnnModuleComboBox.GetPortalDesktopModules()
   at DotNetNuke.Web.UI.WebControls.DnnModuleComboBox.BindAllPortalDesktopModules()
   at DotNetNuke.UI.ControlPanel.AddModule.LoadModuleList() in c:\projects\dnn060104\admin\ControlPanel\AddModule.ascx.cs:line 395
   at DotNetNuke.UI.ControlPanel.AddModule.LoadAllLists() in c:\projects\dnn060104\admin\ControlPanel\AddModule.ascx.cs:line 327
   at DotNetNuke.UI.ControlPanel.AddModule.OnLoad(EventArgs e) in c:\projects\dnn060104\admin\ControlPanel\AddModule.ascx.cs:line 133
   --- End of inner exception stack trace ---

Digging through the DNN6 source code, I found the DesktopModuleController class in DotNetNuke_Community_06.01.04_Source \Library \Entities \Modules \DesktopModuleController.cs, where the GetPortalDesktopModules(int) method converts the results of GetPortalDesktopModulesByPortalID(int) into a SortedList<string, PortalDesktopModuleInfo>, the Add() method of which raised an exception, because a FriendlyName collision.

To find the offending modules, use SSMS to navigate to your DNN database, right-click the DesktopModules table and select “Edit top (n) records”. Press the SQL toolbar button and modify the SELECT statement to ORDER BY FriendlyName.

SELECT     TOP (200) *
FROM         DesktopModules 
ORDER BY FriendlyName

Rename one of the FriendlyName values causing the problem.

Next, update the web.config to cause DNN to reload its caches.

Problem fixed 😉


Listing all DotNetNuke Pages with Settings and Permissions

June 6, 2012

To document the pages of a DNN installation, we can access the relevant tables in a DNN database:

Tabs Pages
Roles DNN Roles
Permission Permitted action
TabPermission Assignment of role privileges per page

The SQL statement consists of sub-selects to

  • concatenate the permissions for a role and a page
  • concatenate the role permissions in a page
SELECT TabID, TabName, Title, [PageHeadText], TabPath, 
  IsVisible AS InMenu,
  -- IsSecure, Url, (etc. as needed) 
  SkinSrc, ContainerSrc,
  SUBSTRING((  
    SELECT '; ' 
      + ISNULL(r.RoleName, CONVERT(NVARCHAR, tp.RoleID)) + ' [' 
      + SUBSTRING((
          SELECT ', ' + PermissionName 
          FROM TabPermission tpSub 
          INNER JOIN Permission p 
            ON p.PermissionID = tpSub.PermissionID
          WHERE tpSub.TabID = tp.TabID 
            AND tpSub.RoleID = tp.RoleID
          FOR XML PATH('')), 
        3, 1000)
      + ']'
    FROM TabPermission tp
    LEFT OUTER JOIN 
      (SELECT RoleID, RoleName FROM Roles
      UNION ALL SELECT -1, 'All Users'
      UNION ALL SELECT -2, 'Registered'
      UNION ALL SELECT -3, 'Unauthorized') r 
        ON tp.RoleID = r.RoleID

    WHERE tp.TabPermissionID IN
      (SELECT MIN(TabPermissionID) 
      FROM  TabPermission tpMin 
      WHERE tpMin.TabID = Tabs.TabID
      GROUP BY tpMin.RoleID
      )
    ORDER BY tp.RoleID
    FOR XML PATH('')), 3, 1000) AS Permissions
FROM Tabs
WHERE PortalID = 0
AND IsDeleted = 0
--AND Level = 0
ORDER BY TabPath

The negative values for RoleID are from an answer on SO.

The query result looks like this (new installation, Admin pages excluded):

TabName TabPath M. SkinSrc ContainerSrc Permissions
About Us //AboutUs 0 [G]Skins/ DarkKnight/ 2-Column-Right-Mega-Menu.ascx [G]Containers/ DarkKnight/ SubTitle_Grey.ascx All Users [View Tab]; Administrators [View Tab, Edit Tab]
Collaborate Details //CollaborateDetails 0 [L]Skins/ Collaborate/ Details.ascx [L]Containers/ Collaborate/ Simple.ascx All Users [View Tab]; Administrators [View Tab, Edit Tab]
Collaborate Home //CollaborateHome 0 [L]Skins/ Collaborate/ Home.ascx [L]Containers/ Collaborate/ Simple.ascx All Users [View Tab]; Administrators [View Tab, Edit Tab]
Getting Started //GettingStarted 1 [G]Skins/ DarkKnight/ Home-Mega-Menu.ascx [G]Containers/ DarkKnight/ SubTitle_Grey.ascx All Users [View Tab]; Administrators [View Tab, Edit Tab]
Home //Home 0 [G]Skins/ DarkKnight/ Home-Mega-Menu.ascx [G]Containers/ DarkKnight/ SubTitle_Grey.ascx All Users [View Tab]; Administrators [View Tab, Edit Tab]
News & Promotions //NewsPromotions 0 [G]Skins/ DarkKnight/ 2-Column-Right-Mega-Menu.ascx [G]Containers/ DarkKnight/ SubTitle_Grey.ascx All Users [View Tab]; Administrators [View Tab, Edit Tab]
Our Services //OurServices 0 [G]Skins/ DarkKnight/ 2-Column-Right-Mega-Menu.ascx [G]Containers/ DarkKnight/ SubTitle_Grey.ascx All Users [View Tab]; Administrators [View Tab, Edit Tab]

(Some column are omitted to fit page width)

Add or remove columns and conditions as required for your purpose.


Automatically updating Custom DotNetNuke Modules using Selenium IDE for Firefox

May 31, 2012

If you develop DNN modules and need to support several installations in sync, any automated help is welcome.

I tried to use Selenium to automate Firefox to upload module packages into a DNN installation. (I did not find any references as to whether DNN has a built-in update mechanism for custom modules). Download Selenium IDE and press Record.

The result is a Selenium Test Case that performs the following operations

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="http://localhost/" />
<title>dnn2ml update BGT.Flash</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">dnn2ml update BGT.Flash</td></tr>
</thead><tbody>
<tr>
	<td>open</td>
	<td>http://localhost/dnn/GettingStarted/tabid/83/ctl/Login/Default.aspx?returnurl=%2fdnn%2fMain.aspx</td>
	<td></td>
</tr>

Retrieve the login URL by right-clicking the Login button and copying the URL. The tabid usually changes between installations.

<tr>
	<td>type</td>
	<td>id=dnn_ctr_Login_Login_DNN_txtUsername</td>
	<td>host</td>
</tr>
<tr>
	<td>type</td>
	<td>id=dnn_ctr_Login_Login_DNN_txtPassword</td>
	<td>PASSWORD</td>
</tr>

Edit Host (Superuser) username and password

<tr>
	<td>clickAndWait</td>
	<td>id=dnn_ctr_Login_Login_DNN_cmdLogin</td>
	<td></td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>//div[@id='dnn_cp_RibbonBar_adminMenus']/ul/li[2]/div/ul/li/ul/li[10]/a/span</td>
	<td></td>
</tr>

I  added these two steps to change to Edit mode (I have no idea how the View/Edit mode is set right after login). This will cause a timeout if DNN is already in Edit mode.

<tr>
	<td>select</td>
	<td>id=dnn_cp_RibbonBar_ddlMode</td>
	<td>label=Edit</td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>css=option[value="EDIT"]</td>
	<td></td>
</tr>

Invoke Install Extension Wizard

<tr>
	<td>click</td>
	<td>link=Install Extension Wizard</td>
	<td></td>
</tr>
<tr>
	<td>type</td>
	<td>id=dnn_ctr_Install_wizInstall_cmdBrowse</td>
	<td>C:\path\to\MyModule\packages\MyModule_00.00.01_Source.zip</td>
</tr>

Select package file to be uploaded

<tr>
	<td>clickAndWait</td>
	<td>id=dnn_ctr_Install_wizInstall_StartNavigationTemplateContainerID_nextButtonStart</td>
	<td></td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>id=dnn_ctr_Install_wizInstall_chkRepairInstall</td>
	<td></td>
</tr>

This is for updating modules, so we need to check the Repair flag

<tr>
	<td>clickAndWait</td>
	<td>id=dnn_ctr_Install_wizInstall_StepNavigationTemplateContainerID_nextButtonStep</td>
	<td></td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>id=dnn_ctr_Install_wizInstall_StepNavigationTemplateContainerID_nextButtonStep</td>
	<td></td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>id=dnn_ctr_Install_wizInstall_StepNavigationTemplateContainerID_nextButtonStep</td>
	<td></td>
</tr>
<tr>
	<td>click</td>
	<td>id=dnn_ctr_Install_wizInstall_chkAcceptLicense</td>
	<td></td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>id=dnn_ctr_Install_wizInstall_StepNavigationTemplateContainerID_nextButtonStep</td>
	<td></td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>//body[@id='Body']/div[4]/div/a[2]</td>
	<td></td>
</tr>
<tr>
	<td>clickAndWait</td>
	<td>id=dnn_LOGIN1_loginLink</td>
	<td></td>
</tr>
<tr>
	<td></td>
	<td></td>
	<td></td>
</tr>
</tbody></table>
</body>
</html>

Logout after upload.

Note that this simple script expects the browser to be logged out in DNN.

The sample may be made more stable by using conditions such as provided by Selenium IDE Flow Control.


DNN 6.0 “The Child Site Name You Specified Already Exists”

May 24, 2012

Just tried to test the DotNetNuke 6.0.4 “export/import templates” functionality in preparation of the deployment process.

To export your site, go to Host/Site Management, hover over Manage, and select the Export Site Template menu. Select the site, enter template file name and description, check “Include Content”, and hit Export Template. (source)

This will create the files mytemplatefilename.export.data.template (XML file) and mytemplatefilename.export.data.template.resources (zip file) in the DNN directory Portals/_default.

To import, click the Add New Site menu on the Manage button. Select the saved template from the dropdown box. (source)

Attention: If you happen to enter a trailing slash in the Site Alias field, DNN displays the following error message:

The Child Site Name You Specified Already Exists. Please Choose A Different Child Site Name.

Which is funny, because you cannot enter a site name. Nowhere. Ever. It’s always Site Alias, Title, but never Name.

Fortunately, I found this bug report stating exactly the problem I had. I had already checked file system permissions etc., but did not find anything obvious. Removing the trailing slash allowed me to create the cloned site.


Content Localization in Multi-Language DotNetNuke 6

May 4, 2012

When we talk about multi-language support by applications (also known as NLS, globalization, localization, internationalization), we need to distinguish between localization of the application and localization of the content (at least).

Enabling

DotNetNuke language packs take care of localization of the application. Once installed and enabled, the application (i.e. the menus, dialogs, prompts, labels) are displayed in the selected language (only if a translation is provided by the LP, of course).

To enable multi-language support in DNN, log in and navigate to

  • Admin/Languages: Install Language Pack
  • Enable Browser Language Detection (if desired)
  • In Edit Mode: Manage/Settings/Portal Language Settings:
  • Enable Language Parameter in URLs? check

This causes DNN to detect the desired language by parsing the URL string for a locale identifier, such as /en-us/ etc. at the begin of the local URL path. Additionally, the ?language=[locale identifier] query string parameter can be used to set the language. Note that the language in the path overrides the language parameter.

Warning: the language parameter is case-sensitive: http://www.dotnetnuke.com/Resources/Forums/forumid/77/threadid/453757/scope/posts.aspx#tblCommand_453757

Once LPs are installed and configured, there are two ways to implement multi-language content in DNN:

Content Localization

Content localization is a built-in functionality of DNN6, and can be enabled by navigating to

  • Host/Host settings/Other settings/Allow Content Localization: check
  • Admin/Languages: Enable Localized Content

Warning: This action is NOT REVERSIBLE, and DNN warns you about that.

Once Content Localization is enabled, the system keeps track of all pages and modules and their translation status. Initially, every page is copied to every installed language. Translations can be edited and published on page and module level. See this DNN video demonstration of the Content Localization feature.

Localization-aware Modules

The other method to implement multi-language capabilities is to develop and/or install ML-aware modules, such as the Nuntio Content HTML module.

It allows editing language-related content for all enabled languages in a single module. Content display is controlled by the currently selected language in DNN.


Moving / Copying DNN6 Installations

May 3, 2012

Before you start actually moving or copying a DotNetNuke 6 installation, you should prepare the DNN database to recognize the new host and application name.

Under Admin/Site Settings/Site Aliases click on Add New Alias, and add the new hostname/applicationname (or just hostname, if the new installation is directly under web root). (found on SO)

Next, copy the DNN directory to the new location, and add a DNN web application in IIS pointing to the new location. Make sure the IIS user (typically IUSR) is allowed to access the file system directories.

In SQL Server Management Studio, backup the original database. Create a new database and restore from the backup. If the database is restored to a different server, make sure the DNN user is able to access the new database.

Adjust the 2 connection strings to point to the new database.

Everything should run fine by now.

If you somehow forgot to add the new host aliases to DNN database, the DNN web application will not startup and present an error message indicating that the host/application names do not match any portals.

Locate the new database in SSMS, and open the PortalAlias table in edit mode.

Update the column HTTPAlias of the migrated portal(s) to the root URL of the new DNN installation.

Open the PortalSettings table in edit mode and change the SettingValue column of SettingName = ‘DefaultPortalAlias’ for the portal(s) to the same values.

Restart the web application. DNN should run now.