Modules represent a discrete set of functionality that can extend the portal framework. In past versions of DotNetNuke, module interactions with the portal were primarily limited to making method calls into the core portal APIs. Though this one-way interaction provides some capability to use portal services and methods within the module, it limits the capability of the portal to provide more advanced services.
To provide two-way interactions with modules, the portal needs to have a mechanism to make method calls into the module. There are several distinct mechanisms for allowing a program to call methods on an arbitrary set of code, where the module code is unknown at the time the portal is being developed. Three of these "calling" mechanisms are used within DotNetNuke:
Inheritance
Delegates
Interfaces
As discussed previously, every module inherits from the PortalModuleBase class (located in the components/module directory). This base class provides a common set of methods and properties that can be used by the module as well as the portal to control the behavior of each module instance. Because the module must inherit from this class, the portal has a set of known methods that it can use to control the module. The portal could extend the base class to add methods to handle new services. One downside to this approach is that there is not an easy mechanism for determining whether a subclass implements custom logic for a specific method or property. Because of this restriction, inheritance is generally limited to providing services that are needed or required for every subclass.
A second method for interacting with the modules involves the use of delegates. A delegate is essentially a pointer to a method that has a specific set of parameters and return type. Delegates are useful when a service can be implemented with a single method call and are the underlying mechanism behind VB.NET's event handling. DotNetNuke uses delegates to implement callback methods for the Module Action menu action event. Although delegates are very useful in some situations, they are more difficult to implement and understand than alternative methods.
The third calling mechanism used by DotNetNuke is the use of interfaces. An interface defines a set of methods, events, and properties without providing any implementation details for these elements. Any class that implements an interface is responsible for providing the specific logic for each method, event, and property defined in the interface. Interfaces are especially useful for defining optional services that a module may implement. The portal can detect if a class implements a specific interface and can then call any of the methods, events, or properties defined in the interface.
Starting in version 3.0, DotNetNuke significantly extended its use of module interfaces. Six main interfaces are intended for use by modules:
IActionable
IPortable
IUpgradeable
IModuleCommunicator
IModuleListener
ISearchable
Every module has a menu that contains several possible action items for activities like editing module settings, module movement, and viewing help. These menu items are called Module Actions. The module menu can be extended with your own custom actions. When your module inherits from the PortalModuleBase class, it receives a default set of actions, which are defined by the portal to handle common editing functions. Your module can extend these actions by implementing the IActionable interface.
As shown in Listing 8-18, the IActionable interface consists of a single method that returns a collection of Module Actions. The ModuleActions property is used when DotNetNuke renders the module.
Listing 8-18: IActionable Interface Definition
Namespace DotNetNuke.Entities.Modules Public Interface IActionable ReadOnly Property ModuleActions() As Actions.ModuleActionCollection End Interface End Namespace
Listing 8-19 shows an example usage as implemented in the Announcements module. The first two lines tell the compiler that this method implements the ModuleAction method of the IActionable interface. It is a read-only method, so you only need to provide a Get function. The first step is to create a new collection to hold the custom actions. Then you use the collection's Add method to create a new action item in the collection. Finally, you return the new collection.
Listing 8-19: IActionable.ModuleActions Example
Public ReadOnly Property ModuleActions() As ModuleActionCollection _ Implements IActionable.ModuleActions Get Dim Actions As New ModuleActionCollection Actions.Add(GetNextActionID, _ Localization.GetString(ModuleActionType.AddContent, _ LocalResourceFile), _ ModuleActionType.AddContent, _ "", _ "", _ EditUrl(), _ False, _ Security.SecurityAccessLevel.Edit, _ True, _ False) Return Actions End Get End Property
This is a simple example that demonstrates the basic steps to follow for your own custom module menus. DotNetNuke provides extensive control over each Module Action.
To take full advantage of the power provided by Module Actions and the IActionable interface, you need to examine the classes, properties, and methods that make up the Module Action API.
Table 8-7 lists the classes that comprise the Module Action API.
Class | Description |
---|---|
ModuleAction | Defines a specific function for a given module. Each module can define one or more actions that the portal will present to the user. Each module container can define the skin object used to render the Module Actions. |
ModuleActionType | Defines a set of constants used for distinguishing common action types. |
ModuleActionCollection | A collection of Module Actions. |
ModuleActionEventListener | Holds callback information when a module registers for Action events. |
ActionEventArgs | Passes data during the click event that is fired when a Module Action is selected by the user. |
ActionEventHandler | A delegate that defines the method signature required for responding to the Action event. |
ActionBase | Creates ModuleAction skin objects. The core framework includes three different implementations: SolPartActions.ascx, DropDown-Actions.ascx, and LinkActions.ascx. |
The ModuleAction class is the heart of the API. Tables 8-8 and 8-9 show the properties and methods available in the ModuleAction class. Each item in the Module Action menu is represented by a single ModuleAction instance.
Property | Type | Description |
---|---|---|
Actions | ModuleActionCollection | Contains the collection of Module Action items that can be used to form hierarchical menu structures. Every skin object that inherits from Action-Base may choose how to render the menu based on the capability to support hierarchical items. For example, the default SolpartActions skin object supports submenus, while the DropDownActions skin object only supports a flat menu structure. |
Id | Integer | Every Module Action for a given module instance must contain a unique Id. The PortalModuleBase class defines the GetNextActionId method, which can be used to generate unique Module Action IDs. |
CommandName | String | Distinguishes which Module Action triggered an action event. DotNetNuke includes 19 standard ModuleActionTypes that provide access to standard functionality. Custom Module Actions can use their own string to identify commands recognized by the module. |
CommandArgument | String | Provides additional information during action event processing. For example, the DotNetNuke core uses CommandArgument to pass the Module ID for common commands like DeleteModule .Action. |
Title | String | Sets the text that is displayed in the Module Action menu. |
Icon | String | Name of the Icon file to use for the Module Action item. |
Url | String | When set, this property allows a menu item to redirect the user to another web page. |
ClientScript | String | JavaScript to run during the menuClick event in the browser. If the ClientScript property is present, it is called prior to the postback occurring. If the ClientScript returns false, the postback is canceled. |
UseActionEvent | Boolean | Causes the portal to raise an Action Event on the server and notify any registered event listeners. If UseActionEvent is false, the portal handles the event, but does not raise the event back to any event listeners. The following CommandNames prevent the Action Event from firing: Module-Help, OnlineHelp, ModuleSettings, Delete-Module, PrintModule, ClearCache, MovePane, MoveTop, MoveUp, MoveDown, and MoveBottom. |
Secure | SecurityAccessLevel | Determines the required security level of the user. If the current user does not have the necessary permissions, the Module Action is not displayed. |
Visible | Boolean | If set to false, the Module Action will not be displayed. This property enables you to control the visibility of a Module Action based on custom business logic. |
NewWindow | Boolean | Forces an action to open the associated URL in a new window. This property is not used if UseActionEvent is true or if the following CommandNames are used: ModuleHelp, Online Help, ModuleSettings, or PrintModule. |
Method | Return Type | Description |
---|---|---|
HasChildren | Boolean | Returns true if the ModuleAction .Actions property has any items (Actions.Count > 0). |
DotNetNuke includes several standard Module Actions that are provided by the PortalModuleBase class or that are used by several of the core modules. These ModuleActionTypes are shown in Listing 8-20. ModuleActionTypes can also be used to access localized strings for the ModuleAction.Title property. This helps promote a consistent user interface for both core and third-party modules.
Listing 8-20: ModuleActionTypes
Public Class ModuleActionType Public Const AddContent As String = "AddContent.Action" Public Const EditContent As String = "EditContent.Action" Public Const ContentOptions As String = "ContentOptions.Action" Public Const SyndicateModule As String = "SyndicateModule.Action" Public Const ImportModule As String = "ImportModule.Action" Public Const ExportModule As String = "ExportModule.Action" Public Const OnlineHelp As String = "OnlineHelp.Action" Public Const ModuleHelp As String = "ModuleHelp.Action" Public Const PrintModule As String = "PrintModule.Action" Public Const ModuleSettings As String = "ModuleSettings.Action" Public Const DeleteModule As String = "DeleteModule.Action" Public Const ClearCache As String = "ClearCache.Action" Public Const MoveTop As String = "MoveTop.Action" Public Const MoveUp As String = "MoveUp.Action" Public Const MoveDown As String = "MoveDown.Action" Public Const MoveBottom As String = "MoveBottom.Action" Public Const MovePane As String = "MovePane.Action" Public Const MoveRoot As String = "MoveRoot.Action" End Class
DotNetNuke provides standard behavior for the following ModuleActionTypes: ModuleHelp, OnlineHelp, ModuleSettings, DeleteModule, PrintModule, ClearCache, MovePane, MoveTop, MoveUp, MoveDown, and MoveBottom. All ModuleActionTypes in this subset will ignore the UseActionEvent and NewWindow properties. The ModuleActionTypes can be further subdivided into three groups:
Basic redirection: The ModuleActionTypes that perform simple redirection and cause the user to navigate to the URL identified in the URL property: ModuleHelp, OnlineHelp, ModuleSettings, and PrintModule.
Module movement: The ModuleActionTypes that change the order or location of modules on the current page: MovePane, MoveTop, MoveUp, MoveDown, and MoveBottom.
Custom logic: The ModuleActionTypes with custom business logic that use core portal APIs to perform standard module-related actions: DeleteModule and ClearCache.
DotNetNuke uses a custom collection class for working with Module Actions. The ModuleActionCollection inherits from .Net System.Collections.CollectionBase class and provides a strongly typed collection class. That minimizes the possibility of typecasting errors that can occur when using generic collection classes such as ArrayList.
Most module developers only need to worry about creating the ModuleActionCollection to implement the IActionable interface. Listing 8-21 shows the two primary methods for adding ModuleActions to the collection. These methods wrap the ModuleAction constructor method calls.
Listing 8-21: Key ModuleActionCollection Methods
Public Function Add(ByVal ID As Integer, _ ByVal Title As String, _ ByVal CmdName As String, _ Optional ByVal CmdArg As String = "", _ Optional ByVal Icon As String = "", _ Optional ByVal Url As String = "", _ Optional ByVal UseActionEvent As Boolean = False, _ Optional ByVal Secure As SecurityAccessLevel = SecurityAccessLevel.Anonymous, _ Optional ByVal Visible As Boolean = True, _ Optional ByVal NewWindow As Boolean = False) _ As ModuleAction Public Function Add(ByVal ID As Integer, _ ByVal Title As String, _ ByVal CmdName As String, _ ByVal CmdArg As String, _ ByVal Icon As String, _ ByVal Url As String, _ ByVal ClientScript As String, _ ByVal UseActionEvent As Boolean, _ ByVal Secure As SecurityAccessLevel, _ ByVal Visible As Boolean, _ ByVal NewWindow As Boolean) _ As ModuleAction
Note | The first method in Listing 8-21 uses optional parameters that are not supported by C#. This method is likely to be deprecated in future versions to simplify support for C# modules and its use is not recommended. |
The ModuleAction framework makes it easy to handle simple URL redirection from a Module Action. Just like the Delete and ClearCache actions provided by the DotNetNuke framework, your module may require the use of custom logic to determine the appropriate action to take when the menu item is clicked. To implement custom logic, the module developer must create a response to a menu click event.
In the DotNetNuke architecture, the ModuleAction menu is a child of the module container. The module is also a child of the container. This architecture allows the framework to easily change out the menu implementation; however, it complicates communication between the menu and module. The menu never has a direct reference to the module and the module does not have a direct reference to the menu. This is a classic example of the Mediator design pattern. This pattern is designed to allow two classes without direct references to communicate. Figure 8-3 shows the steps involved to implement this pattern.
Figure 8-3
The following sections examine those steps and explore ways you can extend the framework.
The first step to implementing the Mediator pattern is to provide a mechanism for the module to register with the portal. The portal will use this information later when it needs to notify the module that a menu item was selected. Handling the click event is strictly optional. Your module may choose to use standard MenuActions, in which case you can skip this step. Because the module does not contain a direct reference to the page on which it is instantiated, you need to provide a registration mechanism.
The Skin class, which acts as the mediator, contains the RegisterModuleActionEvent method, which allows a module to notify the framework of the event handler for the action event (see Listing 8-22). Registration should occur in the module's Page_Load event to ensure that it happens before the event can be fired in the Skin class. The code in Listing 8-22 is from the HTML module and provides a working example of module-based event registration for the ModuleAction event. Although you could use another interface to define a known method to handle the event, the registration mechanism turns out to be a much more flexible design when implementing a single method.
Listing 8-22: Registering an Event Handler
'---------------------------------------------------------------------------------- '- Menu Action Handler Registration - '---------------------------------------------------------------------------------- 'This finds a reference to the containing skin Dim ParentSkin As UI.Skins.Skin = UI.Skins.Skin.GetParentSkin(Me) 'We should always have a ParentSkin, but need to make sure If Not ParentSkin Is Nothing Then 'Register our EventHandler as a listener on the ParentSkin so that it may 'tell us when a menu has been clicked. ParentSkin.RegisterModuleActionEvent(Me.ModuleId, AddressOf ModuleAction_Click) End If '----------------------------------------------------------------------------------
Listing 8-23 shows the ModuleAction_Click event handler code from the HTML module.
Listing 8-23: Handling the Event
Public Sub ModuleAction_Click(ByVal sender As Object, _ ByVal e As Entities.Modules.Actions.ActionEventArgs) 'We could get much fancier here by declaring each ModuleAction with a 'Command and then using a Select Case statement to handle the various 'commands. If e.Action.Url.Length > 0 Then Response.Redirect(e.Action.Url, True) End If End Sub
The DotNetNuke framework uses a delegate (see Listing 8-24) to define the method signature for the event handler. The RegisterModuleActionEvent requires the address of a method with the same signature as the ActionEventHandler delegate.
Listing 8-24: ActionEventHandler Delegate
Public Delegate Sub ActionEventHandler(ByVal sender As Object, _ ByVal e As ActionEventArgs)
Now that the skin (the Mediator class) can communicate with the module, you need a mechanism to allow the menu to communicate with the skin as well. This portion of the communication chain is much easier to code. Handling the actual click event and passing it to the skinning class is the responsibility of the ModuleAction rendering code.
Like much of DotNetNuke, the ModuleAction framework supports the use of custom extensions. In this case, skin objects handle rendering the Module Actions. Each ModuleAction skin object inherits from the DotNetNuke.UI.Containers.ActionBase class. The Skin class retrieves the Module Action collection from the module by calling the IActionable.ModuleActions property and passes the collection to the ModuleAction skin object for rendering. The ActionBase class includes the code necessary to merge the standard Module Actions with the collection provided by the Skin class.
Each skin object includes code in the pre-render event to convert the collection of Module Actions into an appropriate format for display using an associated server control. In the case of SolPartActions.ascx, the server control is a menu control that is capable of fully supporting all of the features of ModuleActions including submenus and icons. Other skin objects like the DropDownActions.ascx may only support a subset of the Module Action features (see Table 8-10).
Action Skin Object | Menu Separator | Icons | Submenus | Client-Side JavaScript |
---|---|---|---|---|
Actions or SolPartActions | Yes | Yes | Yes | Yes |
DropDownActions | Yes | No | No | Yes |
LinkActions | No | No | No | No |
Each skin object handles the click event of the associated server control. This event, shown in Listing 8-25, calls the ProcessAction method, which is inherited from the ActionBase class. ProcessAction is then responsible for handling the event as indicated by the ModuleAction properties. If you create your own ModuleAction skin object, follow this pattern.
Listing 8-25: Click Event Handler
Private Sub ctlActions_MenuClick(ByVal ID As String) Handles ctlActions.MenuClick Try ProcessAction(ID) Catch exc As Exception 'Module failed to load ProcessModuleLoadException(Me, exc) End Try End Sub
If the UseActionEvent is set to True, the ProcessAction method (see Listing 8-26) calls the OnAction method to handle actually raising the event (see Listing 8-27). This might seem like an extra method call when ProcessAction could just raise the event on its own. The purpose of OnAction, though, is to provide an opportunity for subclasses to override the default event handling behavior. Although this is not strictly necessary, it is a standard pattern in .NET and is a good example to follow when developing your own event handling code.
Listing 8-26: ProcessAction Method
Public Sub ProcessAction(ByVal ActionID As String) If IsNumeric(ActionID) Then Dim action As ModuleAction = GetAction(Convert.ToInt32(ActionID)) Select Case action.CommandName Case ModuleActionType.ModuleHelp DoAction(action) Case ModuleActionType.OnlineHelp DoAction(action) Case ModuleActionType.ModuleSettings DoAction(action) Case ModuleActionType.DeleteModule Delete(action) Case ModuleActionType.PrintModule DoAction(action) Case ModuleActionType.ClearCache ClearCache(action) Case ModuleActionType.MovePane MoveToPane(action) Case ModuleActionType.MoveTop, _ ModuleActionType.MoveUp, _ ModuleActionType.MoveDown, _ ModuleActionType.MoveBottom MoveUpDown(action) Case Else ' custom action If action.Url.Length > 0 And action.UseActionEvent = False Then DoAction(action) Else ModuleConfiguration)) End If End Select End If End Sub
Listing 8-27: OnAction Method
Protected Overridable Sub OnAction(ByVal e As ActionEventArgs) RaiseEvent Action(Me, e) End Sub
Because the skin maintains a reference to the ModuleAction skin object, the Skin class can handle the Action event raised by the skin object. As shown in Listing 8-28, the Skin class iterates through ActionEventListeners to find the associated module event delegate. When a listener is found, the code invokes the event, which notifies the module that the event has occurred.
Listing 8-28: Skin Class Handles the ActionEvent
Public Sub ModuleAction_Click(ByVal sender As Object, ByVal e As ActionEventArgs) 'Search through the listeners Dim Listener As ModuleActionEventListener For Each Listener In ActionEventListeners 'If the associated module has registered a listener If e.ModuleConfiguration.ModuleID = Listener.ModuleID Then 'Invoke the listener to handle the ModuleAction_Click event Listener.ActionEvent.Invoke(sender, e) End If Next End Sub
You are now ready to take full advantage of the entire ModuleAction API to create custom menu items for your own modules, handle the associated Action event when the menu item is clicked, and create your own custom ModuleAction skin objects.
DotNetNuke provides the capability to import and export modules within the portal. Like many features in DotNetNuke, it is implemented using a combination of core code and module-specific logic. The IPortable interface defines the methods required to implement this feature on a module-by-module basis (see Listing 8-29).
Listing 8-29: IPortable Interface Definition
Public Interface IPortable Function ExportModule(ByVal ModuleID As Integer) As String Sub ImportModule(ByVal ModuleID As Integer, _ ByVal Content As String, _ ByVal Version As String, _ ByVal UserID As Integer) End Interface
This interface provides a much-needed feature to DotNetNuke and is a pretty straightforward interface to implement. To fully support importing and exporting content, implement the interface within your module's business controller class.
As modules are being loaded by the portal for rendering a specific page, they are checked to determine whether they implement the IPortable interface. To simplify checking whether a module implements the interface, a shortcut property has been added to the ModuleInfo class. The ModuleInfo class provides a consolidated view of properties related to a module. When a module is first installed in the portal, a quick check is made to determine if the module implements the IPortable interface, and if so, the IsPortable flag is set on the base ModuleDefinition record. This property allows the portal to perform the interface check without unnecessarily loading the business controller class. Adding the check at the point of installation removes a requirement by previous DotNetNuke versions for a module control to implement unused stub methods. If the control implements the IPortable interface, DotNetNuke automatically adds the Import Content and Export Content menu items to your Module Action menu (see Figure 8-4).
Figure 8-4
Each module should include a controller class that is identified in the BusinessControllerClass property of the portal's ModuleInfo class. This class is identified in the module manifest file discussed later in the book. The controller class is where you implement many of the interfaces available to modules.
Adding the IPortable interface to your module requires implementing logic for the ExportModule and ImportModule methods shown in Listing 8-30 and Listing 8-31, respectively.
Listing 8-30: ExportModule Stub
Public Function ExportModule(ByVal ModuleID As Integer) As String _ Implements Entities.Modules.IPortable.ExportModule Dim strXML As String = "" Dim objHtmlText As HtmlTextInfo = GetHtmlText(ModuleID) If Not objHtmlText Is Nothing Then strXML += "<htmltext>" strXML += "<desktophtml>{0}</desktophtml>" strXML += "<desktopsummary>{1}</desktopsummary>" strXML += "</htmltext>" String.Format(strXML, _ XMLEncode(objHtmlText.DeskTopHTML), _ XMLEncode(objHtmlText.DesktopSummary)) End If Return strXML End Function
Listing 8-31: ImportModule Stub
Public Sub ImportModule(ByVal ModuleID As Integer, _ ByVal Content As String, _ ByVal Version As String, _ ByVal UserId As Integer) _ Implements Entities.Modules.IPortable.ImportModule Dim xmlHtmlText As XmlNode = GetContent(Content, "htmltext") Dim objText As HtmlTextInfo = New HtmlTextInfo objText.ModuleId = ModuleID objText.DeskTopHTML = xmlHtmlText.SelectSingleNode("desktophtml").InnerText objText.DesktopSummary = xmlHtmlText.SelectSingleNode("desktopsummary").InnerText objText.CreatedByUser = UserId AddHtmlText(objText) End Sub
The complexity of the data model for your module determines the difficulty of implementing these methods. Take a look at a simple case as implemented by the HTMLText module.
In Listing 8-30, the ExportModule method is used to serialize the content of the module to an XML string. DotNetNuke saves the serialized string along with the module's FriendlyName and Version. The XML file is saved into the portal directory.
The ImportModule method in Listing 8-31 reverses the process by deserializing the XML string created by the ExportModule method and replacing the content of the specified module. The portal passes the version information stored during the export process along with the serialized XML string.
The IPortable interface is straightforward to implement and provides much needed functionality to the DotNetNuke framework. It is at the heart of DotNetNuke's templating capability and therefore is definitely an interface that all modules should implement.
One of DotNetNuke's greatest features is the capability to easily upgrade from one version to the next. The heart of that is the creation of script files that can be run sequentially to modify the database schema and move any existing data to the new version's schema. In later versions, DotNetNuke added a mechanism for running custom logic during the upgrade process. Unfortunately, this mechanism was not provided for modules. Therefore, third-party modules were forced to create their own mechanism for handling custom upgrade logic.
This was fixed in DotNetNuke 3.0 and updated again in 4.1. The IUpgradeable interface (see Listing 8-32) provides a standard upgrade capability for modules, and uses the same logic as used in the core frame-work. The interface includes a single method, UpgradeModule, which enables the module to execute custom business logic depending on the current version of the module being installed.
Listing 8-32: IUpgradeable Interface
Public Interface IUpgradeable Function UpgradeModule(ByVal Version As String) As String End Interface
UpgradeModule is called once for each script version included with the module. It is called only for script versions that are later than the version of the currently installed module.
Important | Due to the behavior of ASP.NET when a new assembly is added to the \bin directory, the IUpgradeable interface could fail during installation. This behavior has been corrected in the 3.3 and 4.1 releases. If your module needs this interface for proper installation, have your users upgrade to the latest version of DotNetNuke. |
DotNetNuke includes the capability for modules to communicate with each other through the Inter-Module Communication (IMC) framework. The IMC framework enables modules to pass objects rather than simple strings. Additionally, other properties enable a module to identify the Sender, the Target, and the Type of message. Take a look at the two main interfaces that provide this functionality to your module: ModuleCommunicator and IModuleListener.
The IModuleCommunicator interface defines a single event, ModuleCommunication, for your module to implement (see Listing 8-33).
Listing 8-33: IModuleCommunicator Interface
Public Interface IModuleCommunicator Event ModuleCommunication As ModuleCommunicationEventHandler End Interface
To communicate with another module, first implement the IModuleCommunicator interface in your module. You should have an event declaration in your module as shown in Listing 8-34.
Listing 8-34: ModuleCommunication Event Implementation
Public Event ModuleCommunication(ByVal sender As Object, _ ByVal e As ModuleCommunicationEventArgs) _ Implements IModuleCommunicator.ModuleCommunication
Whereas the IModuleCommunicator is used for sending messages, the IModuleListener interface (see Listing 8-35) is used for receiving messages.
Listing 8-35: IModuleListener Interface
Public Interface IModuleListener Sub OnModuleCommunication(ByVal s As Object, _ ByVal e As ModuleCommunicationEventArgs) End Interface
This interface defines a single method, OnModuleCommunication, which is called when an IModuleCommunicator on the same page raises the ModuleCommunication event. What you do in response to this event notification is totally up to you.
Note | DotNetNuke does not filter event messages. Any module that implements the IModuleListener interface is notified when the event is raised. It is the responsibility of the module to determine whether it should take any action. |
DotNetNuke provides a robust search API for indexing and searching content in your portal. The API is divided into three distinct parts:
Core search engine
Search data store
Search indexer
Like the ModuleAction framework, the search framework also implements a Mediator pattern. When combined with the Provider pattern, this framework provides lots of flexibility. In Figure 8-5, you can see the relationship between these patterns and the three parts of the search API.
Figure 8-5
The core search engine provides a simple API for calling the IndexProvider and then storing the results using a SearchDataStoreProvider. This API is intended for use by the core framework. Future versions of the API will be extended to allow modules greater control over the indexing process.
DotNetNuke includes a default implementation of the SearchDataStoreProvider, which is meant to provide basic storage functionality, but could be replaced with a more robust search engine. As for other providers, third-party developers are implementing providers for many of the current search engines on the market. You can find links to these providers and more at www.dotnetnuke.com and in the DotNetNuke Marketplace at http://marketplace.dotnetnuke.com.
The IndexingProvider provides an interface between the core search engine and each module. DotNetNuke includes a default provider that indexes module content. This provider can be replaced to provide document indexing, web indexing, or even indexing legacy application content stored in another database. If you decide to replace it, keep in mind that DotNetNuke only allows for the use of a single provider of a given type. This means that if you want to index content from multiple sources, you must implement this as a single provider. Future versions of the framework may be enhanced to overcome this limitation.
When using the ModuleIndexer, you can incorporate a module's content into the search engine data store by implementing the ISearchable interface shown in Listing 8-36.
Listing 8-36: ISearchable Interface
Public Interface ISearchable Function GetSearchItems(ByVal ModInfo As ModuleInfo) As SearchItemInfoCollection End Interface
This interface is designed to allow almost any content to be indexed. By passing in a reference to the module and returning a collection of SearchItems, the modules are free to map their content to each SearchItem as they see fit. Listing 8-37 shows a sample implementation from the Announcements module included with DotNetNuke.
Listing 8-37: Implementing the Interface
Public Function GetSearchItems(ByVal ModInfo As Entities.Modules.ModuleInfo) _ As Services.Search.SearchItemInfoCollection _ Implements Services.Search.ISearchable.GetSearchItems Dim SearchItemCollection As New SearchItemInfoCollection Dim Announcements As ArrayList = GetAnnouncements(ModInfo.ModuleID) Dim objAnnouncement As Object For Each objAnnouncement In Announcements Dim SearchItem As SearchItemInfo With CType(objAnnouncement, AnnouncementInfo) Dim UserId As Integer If IsNumeric(.CreatedByUser) Then UserId = Integer.Parse(.CreatedByUser) Else UserId = 2 End If SearchItem = New SearchItemInfo(ModInfo.ModuleTitle & "- "& .Title, _ ApplicationURL(ModInfo.TabID), _ .Description, _ UserId, _ .CreatedDate, _ ModInfo.ModuleID, _ "Anncmnt" & ModInfo.ModuleID.ToString & "-" & .ItemId, _ .Description) SearchItemCollection.Add(SearchItem) End With Next Return SearchItemCollection End Function
In this code, you make a call to your module's Info class, just as you would when you bind to a control within your ASCX file, but in this case the results are going to populate the SearchItemInfo, which will populate the DNN index with data from the module.
The key to implementing the interface is figuring out how to map your content to a collection of SearchItem Info objects. Table 8-11 lists the properties of the SearchItemInfo class.
Property | Description |
---|---|
SearchItemId | An ID assigned by the search engine. It's used when deleting items from the data store. |
Title | A string that is used when displaying search results. |
Description | Summary of the content and is used when displaying search results. |
Author | Content author. |
PubDate | Date that allows the search engine to determine the age of the content. |
ModuleId | ID of the module whose content is being indexed. |
SearchKey | Unique key that can be used to identify each specific search item for this module. |
Content | The specific content that will be searched. The default search data store does not search on any words that are not in the content property. |
GUID | Another unique identifier that is used when syndicating content in the portal. |
ImageFileId | Optional property used to identify image files that accompany a search item. |
HitCount | Maintained by the search engine and used to identify the number of times that a search item is returned in a search. |
Now that the index is populated with data, users of your portal can search your module's information from a unified interface within DNN.