The DotNetNuke architecture permits the application tiers to be distributed across two servers: the web server and the database server, as shown in Figure 7-3. The web server contains the Presentation, Business Logic, and Data Access Layers. The database server contains the Data Layer.
Figure 7-3
The Presentation Layer provides an interface for clients to access the portal application. This layer consists of the following elements:
Web forms: The primary web form is Default.aspx. This page is the entry point to the application. It is responsible for dynamically loading the other elements of the Presentation Layer. Default.aspx is in the root installation directory.
Skins: The Default.aspx web form loads the skin for the page based on the settings for each page or portal. The base Skin class is in /admin/Skins/Skin.vb.
Containers: The Default.aspx web form also loads the containers for the modules based on the settings for each module, page, and portal. The base Container class is in /admin/Containers/Container.vb.
Module user controls: Modules will have at least a single user control that is the user interface for the module. These user controls are loaded by Default.aspx and embedded within the containers and skin. The module user controls are in .ascx files in /DesktopModules/[module name].
Client-side scripts: There are several client-side JavaScript files that are used by the core userinterface framework. For example, the /DotNetNuke/controls/SolpartMenu/spmenu.js script file is used by the SolPartMenu control. Custom modules can include and reference JavaScript files as well. Client-side JavaScript files that are used by the core are in the /js folder. Some skins may use client-side JavaScript, in which case the scripts are in the skin's installation directory. Any client-side scripts used by modules are located under the module's installation directory.
When visiting a DotNetNuke portal, the web form that loads the portal page is Default.aspx. The code-behind for this page ($AppRoot/Default.aspx.vb) loads the selected skin for the active page. The skin is a user control that must inherit from the DotNetNuke.UI.Skins.Skin base class. The Skin class is where most of the action happens for the Presentation Layer.
First, the Skin class iterates through all of the modules that are associated with the portal page. Each module has a container assigned to it. The container is a visual boundary that separates one module from another. The container can be assigned to affect all modules within the entire portal, all modules within a specific page, or a single module. The Skin class loads the module's container and injects the module control into the container.
Next, the Skin class determines whether the module implements the DotNetNuke.Entities.Modules .iActionable interface. If it does, the Skin class discovers the actions that the module has defined and adds them to the container accordingly.
Then, the Skin class adds references to the module's style sheets to the rendered page. It looks for a file named module.css in the specific module's installation directory. If it exists, the class adds an Html GenericControl to the page to reference the style sheet for the module.
All of this happens within the Skin class in the Init event as shown in Figure 7-4. The final rendering of the contents of a module is handled within each module's event lifecycle.
Figure 7-4
Finally, the code-behind ($AppRoot/Default.aspx.vb) renders the appropriate style-sheet links based on the configuration of the portal and its skin. See Chapter 16 for more details on style sheets and the order in which they are loaded.
The Business Logic Layer provides the business logic for all core portal activity. This layer exposes many services to core and third-party modules. These services include:
Localization
Caching
Exception management
Event logging
Personalization
Search
Installation and upgrades
Membership, roles, and profile
Security permissions
The Business Logic Layer is also home to Custom Business Objects (CBOs), whose fundamental purpose is to store information about an object.
The Data Access Layer provides data services to the Business Logic Layer. It allows for data to flow to and from a data store.
As described earlier in this chapter, the Data Access Layer uses the Provider Model to enable DotNetNuke to support a wide array of data stores. The Data Access Layer consists of two elements:
Data Provider API: An abstract base class that establishes the contract that the implementation of the API must fulfill.
Implementation of Data Provider API: A class that inherits from the Data Provider API class and fulfills the contract by overriding the necessary members and methods.
The core DotNetNuke release provides a Microsoft SQL Server implementation of the Data Provider API.
Beginning with the CBO Controller class, the following code snippets show how the Data Provider API works with the Implementation of the Data Provider API. Listing 7-6 shows how the IDataReader that is sent into CBO.FillObject is a call to DataProvider.Instance().GetFolder(PortalID, FolderPath).
Listing 7-6: The FolderController.GetFolder Method
Public Function GetFolder(ByVal PortalID As Integer, ByVal FolderPath As String) As FolderInfo Return CType(CBO.FillObject(DataProvider.Instance().GetFolder(PortalID, _ FolderPath), GetType(Services.FileSystem.FolderInfo)), FolderInfo) End Function
Figure 7-5 breaks down each of the elements in this method call.
Figure 7-5
The Instance() method returns an instance of the implementation of the Data Provider API, and therefore executes the method in the provider itself. The GetFolder method called in Listing 7-6 is an abstract method that is detailed in Listing 7-7.
Listing 7-7: The DataProvider.GetFolder Abstract Method
Public MustOverride Function GetFolder(ByVal PortalID As Integer, _ ByVal FolderPath As String) As IDataReader
This method is part of the contract between the API and the implementation of the API. It is overridden in the implementation of the API as shown in Listing 7-8.
Listing 7-8: The SQLDataProvider.GetFolder Method
Public Overloads Overrides Function GetFolder(ByVal PortalID As Integer, ByVal _ FolderPath As String) As IDataReader Return CType(SqlHelper.ExecuteReader(ConnectionString, DatabaseOwner & _ ObjectQualifier & "GetFolders", GetNull(PortalID), -1, FolderPath), _ IDataReader) End Function
Listing 7-8 shows a reference to the SqlHelper class, which is part of the Microsoft Data Access Application Block. DotNetNuke uses the Data Access Application Block to improve performance and reduce the amount of custom code required for data access. The Data Access Application Block is a .NET component that works with ADO.NET to call stored procedures and execute SQL commands on Microsoft SQL Server.
The Data Layer provides data to the Data Access Layer. The data store used in the Data Layer must be supported by the implementation of the Data Provider API to fulfill the data requests.
Because the DotNetNuke Data Provider Model is so extensible, there are several Data Providers available, including core-released Data Providers and third-party providers such as Microsoft SQL Server, Firebird, MySQL, and Oracle providers. The core DotNetNuke release provides a Microsoft SQL Server implementation of the Data Provider API (which includes support for Microsoft SQL Server 2005 Express).
Included in the implementation of the API is a collection of scripts that create the database in the Data Layer during the installation process. These scripts collectively create the database tables, stored procedures, and data necessary to run DotNetNuke. The installation scripts are run only during a new installation and are run from the DotNetNuke.Services.Upgrade.Upgrade.InstallDNN method. The scripts are as follows:
DotNetNuke.SetUp.SqlDataProvider: Prepares the database for the installation by dropping some key tables.
DotNetNuke.Schema.SqlDataProvider: Installs the tables and stored procedures.
DotNetNuke.Data.SqlDataProvider: Fills the tables with data.
For subsequent upgrades performed after the initial installation, a collection of scripts that modify the schema or data during the upgrade process is run from the DotNetNuke.Services.Upgrade.Upgrade .UpgradeDNN method. There is one script per baseline version of DotNetNuke. A baseline version is a working version of DotNetNuke that represents some internal milestone. For example, after the core team integrates a major new feature, such as the Member Role Provider, the code is tested, compiled, and zipped for distribution among the Core Team. This doesn't necessarily mean there is one script per released version of DotNetNuke — behind the scenes, we may have several baseline versions before a formal public release.
The file-naming convention includes the version of the script followed by the SqlDataProvider extension. The extension must be the same name as found in the DefaultProvider attribute of the Data Provider's configuration settings in the web.config file. For example, the filename for the upgrade script for upgrading from baseline version 4.0.2 to 4.0.3 is 04.00.03.SqlDataProvider.
When the DotNetNuke application is upgraded to another version, these scripts are executed in logical order according to the version number. Only the scripts with a version number that is less than or equal to the value of the constant DotNetNuke.Common.Globals.glbAppVersion are run. This constant is defined in the /components/Shared/Globals.vb file.
The scripts are written in SQL, but there are two important non-SQL tags used in them: {databaseOwner} and {objectQualifier}. Both of these tags represent a programmatically replaceable element of the script. Earlier in this chapter, Listing 7-1 showed the configuration settings for the Microsoft SQL Server Data Provider implementation that included two XML attributes named databaseOwner and object Qualifier. The databaseOwner attribute defines the database owner to append to data objects in the scripts. The objectQualifier attribute defines a string to prefix the data objects with in the scripts.
For example, Listing 7-9 shows how the GetSearchSettings stored procedure is created in the 03.00.04.SqlDataProvider script.
Listing 7-9: A SqlDataProvider Upgrade Script
CREATE PROCEDURE {databaseOwner}{objectQualifier}GetSearchSettings @ModuleID int AS SELECT tm.ModuleID, settings.SettingName, settings.SettingValue FROM {objectQualifier}Tabs searchTabs INNER JOIN {objectQualifier}TabModules searchTabModules ON searchTabs.TabID = searchTabModules.TabID INNER JOIN {objectQualifier}Portals p ON searchTabs.PortalID = p.PortalID INNER JOIN {objectQualifier}Tabs t ON p.PortalID = t.PortalID INNER JOIN {objectQualifier}TabModules tm ON t.TabID = tm.TabID INNER JOIN {objectQualifier}ModuleSettings settings ON searchTabModules.ModuleID = settings.ModuleID WHERE searchTabs.TabName = N'Search Admin' AND tm.ModuleID = @ModuleID GO
This code looks like SQL with the addition of the two non-SQL tags. The first line creates a new stored procedure:
CREATE PROCEDURE {databaseOwner}{objectQualifier}GetSearchSettings
It is created in the context of the databaseOwner defined in web.config, and the name of the stored procedure is prefixed with the objectQualifier value from web.config.
If in web.config the databaseOwner is set to dbo and the objectQualifier is set to DNN, the preceding line would be programmatically converted to:
CREATE PROCEDURE dbo.DNN_GetSearchSettings
The objectQualifier attribute is useful when you want to maintain multiple instances of DotNetNuke in the same database. For example, you could have a single web server with 10 DotNetNuke installations on it, each using the same database. But you wouldn't want these 10 installations using the same data tables. The objectQualifier attribute adds the flexibility for you to store data from multiple DotNetNuke installations in the same database.