Extending DNN by forking an intrinsic DNN provider
Dot Net Nuke (DNN) as a CMS is developed for expansibility and the core framework is designed to make this achievable. This post does discuss extensibility in detail. It is hoped that it will be found beneficial for analysts or technology advisors whom are considering DNN for porting their businesses on DNN for ease of administration.
It is more or less a practice while developing on top of DNN, not to alter the source code often regarded as following,
“DNN’s core will remain un-altered during the course of development for this project”
Then how do you exactly achieve extensibility if you don’t really change the core? DNN heavily utilize the concept of providers from data access to navigation. As a developer or an architect, if you are restricted by an intrinsic feature of DNN, what you can do is to develop on top of existing framework by forking an existing provider.
By forking we mean that you take the source code of that particular provider (since that is redistributable as being open source), duplicate that yet following the standard inheritance chain in set of classes and create a new provider project. Then you simply plug the new provider entry in the web.config file.
Here we will consider a simple case of forking DNN’s sitemap provider. We only want to filter the Urls listed in sitemap of which parent Tab (DNN’s pages are termed tabs) are of type Url. This is pretty simple and will explain the concept of forking.
At the time of this writing the latest version of DNN’s (5.6.2 beta) sitemap provider can be found at,
http://dotnetnuke.codeplex.com/SourceControl/changeset/view/58582#608945
Duplicate this class in your own provider project. To explain that DNN’s default language (VB) doesn’t limit the extensibility during forking process, I have done so in a C# project,
namespace DNN.Providers.SampleSitemapProvider
{
public class TestSitemapProvider : DotNetNuke.Services.Sitemap.SitemapProvider
{
…
The only change we will have to bring in the code of this file to filter the Urls as per our need will be in method,
private SitemapUrl GetPageUrl(TabInfo objTab, string language)
{
…
Just before the line that says,
pageUrl.Priority = GetPriority(objTab);
place a method call,
//Filter Url
if (!CheckParentTabType(objTab))
{
return null;
}
And introduce a private method in the very file,
private bool CheckParentTabType(TabInfo objTab)
{
try
{
if (objTab != null)
{
if (objTab.ParentId != -1)
{
TabController tabController = new TabController();
TabInfo parentTabInfo = tabController.GetTab(objTab.ParentId);
if (parentTabInfo != null)
{
if (parentTabInfo.TabType == TabType.Url)
{
return true;
}
}
}
}
catch (Exception ex)
{
DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
}
return false;
}
That is all. Verify the build output path points to bin folder and bring following change in provider section for sitemap for this new provider to trigger when sitemap.aspx page is requested,
<sitemap defaultProvider="coreSitemapProvider">
<providers>
<clear />
<add name="coreSitemapProvider" type="DotNetNuke.SitemapProviders.CoreSitemapProvider, DotNetNuke.SitemapProviders.CoreSitemapProvider" providerPath="~\Providers\MembershipProviders\Sitemap\CoreSitemapProvider\" />
</providers>
</sitemap>
So we will only have to add a new node in this section as following,
<add name="sampleSitemapProvider" type="DNN.Providers.SampleSitemapProvider.TestSitemapProvider, TKCarsites.Providers.SampleSitemapProvider" providerPath="~\Providers\MembershipProviders\Sitemap\CoreSitemapProvider\" />
And flip the default sitemap entry from
<sitemap defaultProvider="coreSitemapProvider">
to
<sitemap defaultProvider="SampleSitemapProvider">
For our sitemap to take effect. Off course for a production deployment this makes more sense to be performed using manifest file config section. The complete project is attached for quick review.
Beware of component type 'config' during same package version re-Install
I came across this issue in version 5.4.4 as well as within 5.5.1 (both professional and community editions).
BackgroundIf you have a config type component that adds some entries in web.config file (the configuration example below is taken from DNN Wiki as it is), and you believe you have everything written properly, still pay attention that a re-install of the same package version can lead to corrupted web.config file. What happens is that during re-installation of same package version, the node elements are added twice despite that you have explicitly defined that that in terms of a collision, the key will be overwritten. It seems like that the DNN engine solely skips this part of collision validation and simply adds the key resulting in duplicate keys be added in web.config file which can result in entire website crash (as would be the case in example below or for modules or handlers section within web.config),<component type="Config">
<config>
<configFile>web.config</configFile>
<install>
<configuration>
<nodes>
<node path="/configuration/dotnetnuke/caching/providers"
action="update" key="name" collision="overwrite">
<add name="FileBasedCachingProvider"
type="DotNetNuke.Services.Cache.FileBasedCachingProvider.FBCachingProvider,
DotNetNuke.Caching.FileBasedCachingProvider"
providerPath="~\Providers\CachingProviders\FileBasedCachingProvider\" />
</node>
</nodes>
</configuration>
</install>
<uninstall>
<configuration>
<nodes>
<node path="/configuration/dotnetnuke/caching/providers/
add[@name='FileBasedCachingProvider']"
action="remove" />
</nodes>
</configuration>
</uninstall>
</config>
</component>
Thus re-installation of same package version will add the portion in web.config file each time the re-installation is made under <configuration><dotnetnuke><caching><providers> section. Whenever you are dealing with a config type package, don't re-install the same package version again.
Special thanks to Jubair for his verification of this on professional as well as community edition.
Ref: The config section is taken from Url, http://www.dotnetnuke.com/Community/Wiki/tabid/1409/Page/Manifest-Component-Installers-The-Config-Component/Default.aspx