Understanding the DotNetNuke Core Architecture

0
118
13 min read

Architecture overview

As opposed to traditional web applications that may rely on a multitude of web pages to deliver content, DotNetNuke uses a single main page called Default.aspx. The content for this page is generated dynamically by using a tabID value to retrieve the skin and modules needed to build the page requested, from the DotNetNuke database. Before we move on, we should discuss what is meant by a tab and a page. As you read this article, you will notice the word “tab” is sometimes used when referring to pages in your DotNetNuke portal. In the original IBuySpy application, pages were referred to as tabs because they resembled tabs when added to the page.

IBuySpy application, the skeleton ASP.NET Framework, was created by Microsoft to demonstrate ASP.NET features, and DotNetNuke was originally derived from it.

This continued in the original versions of the DotNetNuke project. Starting with version 3.0, and continuing with version 5.2.x, there has been an ongoing effort to rename most of these instances to reflect what they really are: pages. Most references to “tabs” have been changed to “pages”, but the conversion is not complete. For this reason, you will see both—tabs and pages—in the database, in the project files, and in this text. We will use these terms interchangeably throughout this text as we look into the core architecture of DNN.

We will begin with a general overview of what happens when a user requests a page on your DotNetNuke portal. The process for rendering a page in DotNetNuke works like this: a user navigates to a page on your portal; this calls the Default.aspx page, passing the tabid parameter in the querystring to let the application identify the page being requested. The example http://www.dotnetnuke.com/Default. aspx?tabid=476 demonstrates this.

DotNetNuke 3.2 introduced something called URL rewriting. This takes the querystring shown above and rewrites it so that it is in a format that helps increase search engine hits. We will cover the HTTP module that is responsible for this in more detail later in this article. The rewritten URL would resemble http://localhost/DotNetNuke/Home.aspx. This assumes that the page name for tabid 476 is Home. While referring to URLs in this article we will be using the non-rewritten version of the URL. URL rewriting can be turned off in the friendly URL section of the Host Settings page.

The querystring value (?tabid=476) is sent to the database, where the information required for the page is retrieved, as shown in the following diagram:

The portal that the user is accessing can be determined in a number of ways, but as you can see from the Tabs table (see the following screenshot), each page/tab contains a reference to the portal it belongs to in the PortalID field. Once the server has a reference to the page that the user requested (using the tabID), it can determine what modules belong to that page.

Although there are many more tables involved in this process, you can see that these tables hold not only the page and modules needed to generate the page, but also what pane to place them on (PaneName) and what container skin to apply (ContainerSrc).

All of this information is returned to the web server, and the Default.aspx page is constructed with it and returned to the user who requested it along with the required modules and skins, as shown in the following diagram.

Now, this is of course a very general overview of the process, but as we work through this article, we will delve deeper into the code that makes this process work, and in the end, show a request work its way through the framework to deliver a page to a user.

Diving into the core

There are over 160,000 lines of code in the DotNetNuke application. There is no practical (or even possible) way to cover the entire code base. In this section, we will go in depth into what I believe are the main portions of the code base: the PortalSettings as well as the companion classes found in the portals folder; the web.config file including the HTTP Modules and Providers; and the Global.asax and Globals.vb files.

We will start our discussion of the core with two objects that play an integral part in the construction of the architecture. The Context object and the PortalSettings class will both be referred to quite often in the code, and so it is important that you have a good understanding of what they do.

Using the Context object in your application

ASP .NET has taken intrinsic objects like the Request and the Application objects and wrapped them together with other relevant items into an intrinsic object called Context.

The Context object (HttpContext) can be found in the System.Web namespace. In the following table, you will find some of the objects that make up the HttpContext object.



Title

Description

Application

Gets the HttpApplicationState object for the current HTTP request.

Cache

Gets the Cache object for the current HTTP request.

Current

Gets the HttpContext object for the current HTTP request. This is a static (shared in VB) property of the HttpContext class, through which you access all other instance properties discussed in this table, that together enable you to process and respond to an HTTP request.

Items

Gets a key-value collection that can be used to organize and share data between an IHttpModule and an IHttpHandler during an HTTP request.

Request

Gets the HttpRequest object for the current HTTP request. This is used to extract data submitted by the client, and information about the client itself (for example, IP ), and the current request.

Response

Gets the HttpResponse object for the current HTTP response. This is used to send data to the client together with other response-related information such as headers, cacheability, redirect information, and so on.

Server

Gets the HttpServerUtility object that provides methods used in processing web requests.

Session

Gets the HttpSessionState instance for the current HTTP request.

User

Gets or sets security information for the current HTTP request.

Notice that most of the descriptions talk about the “current” request object, or the “current” response object. The Global.asax file, which we will look at soon, reacts on every single request made to your application, and so it is only concerned with whoever is “currently” accessing a resource.

The HttpContext object contains all HTTP-specific information about an individual HTTP request. In particular, the HttpContext.Current property can give you the context for the current request from anywhere in the application domain. The DotNetNuke core relies on the HttpContext.Current property to hold everything from the application name to the portal settings and through this makes it available to you.

The PortalSettings class

The portal settings play a major role in the dynamic generation of your pages and as such will be referred to quite often in the other portions of the code. The portal settings are represented by the PortalSettings class which you will find in the EntitiesPortalPortalSettings.vb file. As you can see from the private variables in this class, most of what goes on in your portal will at some point needto access this object. This object will hold everything from the ID of the portal to the default language, and as we will see later, is responsible for determining the skins and modules needed for each page.

Private _PortalId As Integer
Private _PortalName As String
Private _HomeDirectory As String
Private _LogoFile As String
Private _FooterText As String
Private _ExpiryDate As Date
Private _UserRegistration As Integer
Private _BannerAdvertising As Integer
Private _Currency As String
Private _AdministratorId As Integer
Private _Email As String
Private _HostFee As Single
Private _HostSpace As Integer
Private _PageQuota As Integer
Private _UserQuota As Integer
Private _AdministratorRoleId As Integer
Private _AdministratorRoleName As String
Private _RegisteredRoleId As Integer
Private _RegisteredRoleName As String
Private _Description As String
Private _KeyWords As String
Private _BackgroundFile As String
Private _GUID As Guid
Private _SiteLogHistory As Integer
Private _AdminTabId As Integer
Private _SuperTabId As Integer
Private _SplashTabId As Integer
Private _HomeTabId As Integer
Private _LoginTabId As Integer
Private _UserTabId As Integer
Private _DefaultLanguage As String
Private _TimeZoneOffset As Integer
Private _Version As String
Private _ActiveTab As TabInfo
Private _PortalAlias As PortalAliasInfo
Private _AdminContainer As SkinInfo
Private _AdminSkin As SkinInfo
Private _PortalContainer As SkinInfo
Private _PortalSkin As SkinInfo
Private _Users As Integer
Private _Pages As Integer

The PortalSettings class itself is simple. It is filled by using one of the constructors that accepts one or more parameters. These constructors then call the private GetPortalSettings method . The method is passed a tabID and a PortalInfo object. You already know that the tabID represents the ID of the page being requested, but the PortalInfo object is something new. This class can be found in the same folder as the PortalSettings class and contains the basic information about a portal such as PortalID, PortalName, and Description.

However, from the PortalSettings object, we can retrieve all the information associated with the portal. If you look at the code inside the constructors, you will see that the PortalController object is used to retrieve the PortalInfo object. The PortalInfo object is saved in cache for the time that is specifi ed on the Host Settings page.

A drop-down box on the Host Settings page (DesktopModulesAdminHostSettingsHostSettings.ascx) is used to set the cache.

  • No caching:0
  • Light caching:1
  • Moderate caching:3
  • Heavy caching:6

The value in this dropdown ranges from 0 to 6; the code in the DataCache object takes the value set in the drop-down and multiplies it by 20 to determine the cache duration. Once the cache time is set, the method checks if the PortalSettings object already resides there. Retrieving these settings from the database for every request would cause your site to run slowly, so placing them in a cache for the duration you select helps increase the speed of your site. Recent versions of DotNetNuke have focused heavily on providing an extensive caching service. An example of this can be seen in the following code:

Dim cacheKey As String = String.Format(DataCache.PortalCacheKey,
PortalId.ToString())
Return CBO.GetCachedObject(Of PortalInfo)
(New CacheItemArgs(cacheKey, DataCache.PortalCacheTimeOut,
DataCache.PortalCachePriority, PortalId),
AddressOf GetPortalCallback)

We can see in the previous code that the CBO object is used to return an object from the cache. CBO is an object that is seen frequently throughout the DotNetNuke core. This object’s primary function is to return the populated business object. This is done in several ways using different methods provided by CBO. Some methods are used to map data from an IDataReader to the properties of a business object. However, in this example, the Get CachedObject method handles the logic needed to determine if the object should be retrieved from the cache or from the database. If the object is not already cached, it will use the GetPortalCallback method passed to the method to retrieve the portal settings from the database. This method is located in the PortalController class (EntitiesPortalPortalController.vb) and is responsible for retrieving the portal information from the database.

Dim portalID As Integer = DirectCast(cacheItemArgs.ParamList(0),
Integer)
Return CBO.FillObject(Of PortalInfo)(DataProvider.Instance _
.GetPortal(portalID, Entities.Host.Host.ContentLocale.ToString))

This will fi ll the PortalInfo object (EntitiesPortalPortalInfo.vb), which as we mentioned, holds the portal information. This object in turn is returned to the GetCachedObject method. Once this is complete, the object is then cached to help prevent the need to call the database during the next request for the portal information. There is also a section of code (not shown) that verifi es whether the object was successfully stored in the cache and adds an entry to the event log if the item failed to be cached.

' if we retrieved a valid object and we are using caching
If objObject IsNot Nothing AndAlso timeOut > 0 Then
' save the object in the cache
DataCache.SetCache(cacheItemArgs.CacheKey, objObject, _
cacheItemArgs.CacheDependency, Cache.NoAbsoluteExpiration, _
TimeSpan.FromMinutes(timeOut), cacheItemArgs.CachePriority, _
cacheItemArgs.CacheCallback)

End If

After the portal settings are saved, the properties of the current tab information are retrieved and populated in the ActiveTab property. The current tab is retrieved by using the tabID that was originally passed to the constructor. This is handled by the VerifyPortalTab method and done by getting a list of all of the tabs for the current portal. Like the portal settings themselves, the tabs are saved in cache to boost performance. The calls to the caching provider, this time, are handled by the TabController (EntitiesTabsTabController.vb). In the last VerifyPortalTab method, the code will loop through all of the host and non-host tabs, returned by the TabController, for the site until the current tab is located.

' find the tab in the portalTabs collection
If TabId <> Null.NullInteger Then
If portalTabs.TryGetValue(TabId, objTab) Then
'Check if Tab has been deleted (is in recycle bin)
If Not (objTab.IsDeleted) Then
Me.ActiveTab = objTab.Clone()
isVerified = True
End If
End If
End If
' find the tab in the hostTabs collection
If Not isVerified AndAlso TabId <> Null.NullInteger Then
If hostTabs.TryGetValue(TabId, objTab) Then
'Check if Tab has been deleted (is in recycle bin)
If Not (objTab.IsDeleted) Then
Me.ActiveTab = objTab.Clone()
isVerified = True
End If
End If
End If

If the tab was not found in either of these collections, the code attempts to use the splash page, home page, or the fi rst page of the non-host pages. After the current tab is located, further handling of some of its properties is done back in the GetPortalSettings method. This includes formatting the path for the skin and default container used by the page, as well as collecting information on the modules placed on the page.

Me.ActiveTab.SkinSrc = _
SkinController.FormatSkinSrc(Me.ActiveTab.SkinSrc, Me)
Me.ActiveTab.SkinPath = _
SkinController.FormatSkinPath(Me.ActiveTab.SkinSrc)

For Each kvp As KeyValuePair(Of Integer, ModuleInfo) In _
objModules.GetTabModules(Me.ActiveTab.TabID)
' clone the module object _
( to avoid creating an object reference to the data cache )
Dim cloneModule As ModuleInfo = kvp.Value.Clone
' set custom properties
If Null.IsNull(cloneModule.StartDate) Then
cloneModule.StartDate = Date.MinValue
End If
If Null.IsNull(cloneModule.EndDate) Then
cloneModule.EndDate = Date.MaxValue
End If
' container
If cloneModule.ContainerSrc = "" Then
cloneModule.ContainerSrc = Me.ActiveTab.ContainerSrc
End If
cloneModule.ContainerSrc = _
SkinController.FormatSkinSrc(cloneModule.ContainerSrc, Me)
cloneModule.ContainerPath = _
SkinController.FormatSkinPath(cloneModule.ContainerSrc)
' process tab panes
If objPaneModules.ContainsKey(cloneModule.PaneName) = False Then
objPaneModules.Add(cloneModule.PaneName, 0)
End If
cloneModule.PaneModuleCount = 0
If Not cloneModule.IsDeleted Then
objPaneModules(cloneModule.PaneName) = _
objPaneModules(cloneModule.PaneName) + 1
cloneModule.PaneModuleIndex = _
objPaneModules(cloneModule.PaneName) - 1
End If
Me.ActiveTab.Modules.Add(cloneModule)
Next

We have now discussed some of the highlights of the PortalSettings object as well as how it is populated with the information it contains. In doing so, we also saw abrief example of the robust caching service provided by DotNetNuke. You will see the PortalSettings class referenced many times in the core DotNetNuke code, so gaining a good understanding of how this class works will help you to become more familiar with the DNN code base. You will also fi nd this object to be very helpful while building custom extensions for DotNetNuke.

The caching provider itself is a large topic, and reaches beyond the scope of this article. However, simply understanding how to work with it in the ways shown in these examples should satisfy the needs of most developers. It is important to note that, you can get any type of object cached by DNN by passing in a key for your object to DataCache.SetCache method, together with the data, and some optional arguments.

While fetching the object back from DataCache.GetCache, you pass in the same key, and check the result. A non-null (non-Nothing in VB) return value means you have fetched the object successfully from the cache, otherwise you would need to fetch it from the database.

LEAVE A REPLY

Please enter your comment!
Please enter your name here