Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

ModuleFactory: A Module Code Generator Framework on DotNetNuke(DNN)

3.65/5 (7 votes)
9 Jul 2008CPOL17 min read 1   281  
This article introduces the architecture of a code generator module on DNN, called ModuleFactory in our company. ModuleFactory is comprised of a variety of module builders. Each module builders’ functionality is to generate codes for newborn modules ab initio based on metadata collected from users,

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

Introduction

Code generation is a mechanism that allows developers to automatically generate consistent codes based on user inputs or metadata data from databases or files. With code generation, developers can circumvent tedious routine work (e.g., writing data access layer classes, manually setting controls’s properties, etc), focusing on domain-specific problems.

In this article, we will introduce the architecture of a code generator module on DNN, called ModuleFactory coined by our company. ModuleFactory is comprised of a variety of module builders. Each module builder's functionality is to generate codes for newborn modules based on metadata collected from users, and registers the new modules to DNN on the fly so that they can immediately run on DNN. Developers can then use VisualStudio (VS) to improve the functionality based on the existing module codes instead of writing/copying repetitive codes and take manual operations to let modules run on DNN. Consequently, the life-cycle of developing modules will be accelerated notably. More importantly, our design of ModuleFactory is based on Open Closed Principle, which enables developers to leverage ModuleFactory’s framework to plug new module builders into ModuleFactory without alter existing codes.

Problem to Solve

As an enterprise developer, you often face problems or requirements with similar characteristics, or patterns. For an MIS developer, for example, a common requirement might be a webpage that shows product sales which can be viewed by managers. To complete this work, you start to write SQL statements, drag grid to the VS design surface for showing data retrieved by SQL, write business logic or validation rules, set the properties of controls regarding rendering appearance, and so forth. If you adopt a DNN solution, you need a further step: creating and registering the module to DNN whereby you can run the module and view its appearance or debug its source codes.

If you have ever had the experience in developing this common scenario, you should be aware that it is impossible to write a code generation tool that can generate total codes for covering all functionality, because much of business logic is domain-specific and often suits for only one special scenario. However, it is viable to generate a portion of complete codes, especially routine work, in a specific application domain.

From a percentage-of-work perspective, an EmptyModule generator that generates a runnable module (without any functionality) on DNN may help you get through 5% of your work (i.e., essential module codes and creating the module on DNN); a grid module generator that generates a grid along with a data-binding setting may reduce your work by 30%. This concept can be visualized in Figure 1, the more the module sits on the left side of the continuum, the more it is general-purpose, and via versa. In general, it is more valuable to write code generators for the left side than to write for the right side in the respect of reuse rate. You can organize the patterns in your application domain, evaluating if it is worthwhile writing code generators for them.

image002.jpg
Figure 1. The spectrum of code generators in the scale of percentage of codes generated

Installation and Usage Instructions

Before elaborating on the system architecture and implementation details, readers are highly recommended to install the ModuleFactory module. Everything required to install ModuleFactory is provided in the download where you will see ModuleFactory.zip and the trial version of Telerik.Web.UI.dll. As other common DNN modules, you install ModuleFactory.zip to DNN as described here. In addition, because one of module builders uses Telerik’s RadControls, you need to put Telerik.Web.UI.dll into the bin directory.

ModuleFactory contains two module builders for demonstration, EmptyModuleBuilder and GridModuleBuilder, to generate two kinds of modules: one is an empty module without any functionality and the other contains a Telerik’s RadGrid, by which users can view and update data retrieved by a select SQL. As promised, you do not need to take care of any manual operation to let the module generated by the two module builders run on DNN. Take EmptyModuleBuilder as an example (see top portion of Figure 2), the only necessary parameter is the module name. If you do not provide the module file name for generated files, it will be the same as module name. For instance, if the module name is Foo, the corresponding generated file names will be Foo.ascx, Foo.ascx.cs, Settings.ascx and Settings.ascx.cs (Figure 2). So if you are a DNN developer, you may use EmptyModuleBuilder frequently as a startup. Furthermore, you can switch module builder to GridMoudleBuilder via the dropdown list, by which the bottom portion of ModuleFactory will be changed accordingly. GridMoudleBuilder demonstrates the possibility to generate a full-fledge grid (see bottom portion of Figure 2) and this is a common scenario for MIS programmers.

image004.jpg
Figure 2. Use ModuleFactory to Generate an Empty Module and Grid Module

Architecture and Implementation

To begin with, readers are highly recommended to install the ModuleFacotry.zip (described at preceding section) to follow. We will use it as examples throughout this section.

The outline of this section is as follows. At first, we explain and reinforce terminology critical for discussing technical details. Then, we discuss the architecture of how to dynamically load module builders (i.e., module code generators) through which developers can plug new module builders into ModuleFactory without modifying existing codes. Next, we show you how to design and implement EmptyModuleBuilder. Although EmptyModuleBuilder functionality is pretty simple, the architecture and libraries derived from the process of building it can be reused via other module builders, like GridModuleBuilder explained in the next section.

Terminology

  • Module Builder: A module code generator for a specific application, which is comprised of a group of UserControls that implementing specific interfaces and can be dynamically loaded into ModuleFactory module.
  • ModuleFactory: A DNN module comprised of Module Builders.
  • Module Entry Control: The first UserControl (i.e., PortalModuleBase) that users will interact when navigating pages where modules reside.

Design of Loading Module Builders Dynamically

Since you can not predict how many module builders you will develop ahead of time, there must be a mechanism to let you and other developers to plug their new builder into ModuleFactory without modifying existing codes. This mechanism can be generalized as Open Closed Principle in OOP realm. In this project, we employ a Provider-like pattern to implement the mechanism. First, we force each builder’s entry control to extend UserControl so that it can be dynamically loaded with Page.LoadControl(). Second, any module builder (i.e., UserControl) must implement IModuleBuilder interface (Listing 1) that specifies the contract of what a module builder should afford. The IModuleFilesGenerator declared in IModuleBuilder is another crucial interface that we will discuss it later. Third, the available module builders of ModuleFactory are recorded at ModuleFactory.config (Listing 2) that is parsed by the ModuleBuilderLoader class. In conclusion, a UserControl that implements IModuleBuilder is a concrete Provider which can be plug into ModuleFactory through ModuleFactory.config. After leafing through the key elements to implement dynamically loading module builders, let’s plunge into the details.

Listing 1. IModuleBuilder

C#
namespace ModuleBuilder
{
    public interface IModuleBuilder
    {
        IModuleFilesGenerator ModuleFilesGenerator  { get; set;}
        string ModuleName { get; set;}
        string ModuleFileName{ get; set;}
        string ModuleDescription { get; set;}
    }
}

Listing 2. ModuleFactory.config

C#
<moduleBuilders>
    <add source="EmptyModuleBuilder/EmptyModuleBuilder.ascx"
         friendlyName = "Empty module builder"></add>
</moduleBuilders>

The ModuleBuilderLoader is implemented as Singleton and the primary logic is located at LoadBuilder(string controlName) shown in Listing 3. One caution of LoadBuilder is to replace periods with other characters not breaking JavaScript when assigning an ID, and if the UserControl does not implement IModuleBuilder, it will throw an exception. In this way, as a new module builder is available, we can simply update ModuleFactory.setting to let it plugged into ModuleFactory.

Listing 3. Code Snippet of ModuleBuilderLoader

C#
private static ModuleBuilderLoader loader = null;
/// <summary>
/// Singleton method
/// </summary>
/// <returns></returns>
public static ModuleBuilderLoader Instance()
{
    if (uiLoader == null)
    {
        uiLoader = new BuilderUILoader();
    } 
    return uiLoader;
}
 
/// <summary>
/// Load the ModuleBuilder(i.e., .ascx)
/// </summary>
/// <param name="path">the path of .ascx defined at ModuleFactory.config</param>
/// <returns></returns>
public UserControl LoadBuilder(string path)
{
    Page p = (Page)HttpContext.Current.Handler;
 
    string loadPath = Path.Combine(Constants.RELATIVE_ROOT_DIRECTORY, path);
 
    UserControl builder = (UserControl)p.LoadControl(loadPath);
 
    if (!(builder is IModuleBuilder))
    {
        throw new Exception("The UserControl loaded from " + loadPath +
            " does not implement IModuleBuilder.");
    }
 
    // Note: Periods(.) and (/) will break js function at client-side 
    builder.ID = path.Replace('.', '_').Replace('/','_');
    return builder;
}

ModuleBuilderLoader, described above, is used by ModuleFactory’s entry control, namely ModuleBuilderContainer.ascx. ModuleBuilderContainer, as shown in Figure 2, contains a dropdown list and the bottom area to dynamically load module builder selected in dropdown list. It uses ModuleBuilderLoader to dynamically load module builder as shown in Listing 4. The dcpModBuilderArea is the region where module builders are located. The items in ddlBuilders (dropdown list in Figure 2) are populated against the data in ModuleBuilder.setting (Listing 2).

Listing 4. Code snippet of ModuleBuilderContainer.ascx.cs

C#
protected void lbtnChangeBuilder_Click(object sender, EventArgs e)
{
    dcpModBuilderArea.Controls.Clear();
 
    Control loadControl = BuilderUILoader.Instance().LoadBuilder(
        ddlBuilders.SelectedValue);
 
    dcpModBuilderArea.Controls.Add(loadControl);
}

Let’s pause in a minute and summarize the architecture of ModuleFactory explored so far: ModuleBuilderContainer uses ModuleBuilderLoader to load module builders inheriting from UserControl that implements IModuleBuilder. The architecture is illustrated as Figure 4. In the next section, we begin to discuss how to implement EmptyModuleBuilder, a builder that generates a module without any functionality and how it can seamlessly integrates into ModuleFactory with ModuleBuilderContainer plus the setting in Listing 2.

image008.jpg
Figure 4. Partial System Architecture

EmptyModuleBuilder — Create and Register New Modules

The primary feature of EmptyModuleBuilder is to let the module generated be immediately run on DNN without any manual intervention. So let’s first take a quick review on how DNN allows administrators to manually do the work.

The total steps for creating a new DNN module (assume module codes have been in place) can be classified as two categories: create and register. By creating a new module (Figure 5), you just create a definition container describing the new module. Once a module definition container has been created, you can take the “register” step: create an unlimited number of module definitions (Figure 6), and add an unlimited number of PortalModuleBase or any DNN built-in base classes into a module definition (Figure 7). Because all DNN built-in base classes inherit UserControl, they are called module’s control. In summary, a DNN module is composed of a number of module definitions, which contains the metadata of registered module’s controls.

image010.jpg
Figure 5. Create a module

image012.jpg

Figure 6. Add a module definition

image014.jpg

Figure 7. Add a control to a module definition

The available APIs for creating and registering a module can be referred to a DNN built-in module, ModuleDefinitions, located at ~/admin/. If you are familiar with the code regarding the two steps, just jump to next sub-section, “EmptyModuleBuilder – DNNModuleCreator”. Assuming the concrete module files, Foo.ascx plus Foo.ascx.cs, have already been under ~/ DesktopModules/Foo/. A module called Foo can be created following the steps:

  • Create a module
C#
DesktopModuleController deskModCtrl = new DesktopModuleController();

deskModInfo.DesktopModuleID = Null.NullInteger;
deskModInfo.ModuleName = "Foo";
deskModInfo.FolderName = "Foo"
deskModInfo.FriendlyName = "Foo";
deskModInfo.Version = Null.NullString;

deskModInfo.DesktopModuleID = deskModCtrl.AddDesktopModule(deskModInfo);
  • Add module’s controls into a module definition
C#
// Create a new module definition
ModuleDefinitionInfo modDefInfo = new ModuleDefinitionInfo();
modDefInfo.DesktopModuleID = deskModInfo.DesktopModuleID;
// Use module name as definition name
modDefInfo.FriendlyName = "Foo";
modDefInfo.DefaultCacheTime = 0;

 ModuleDefinitionController modDefCtrl = new ModuleDefinitionController();
int modDefId = modDefCtrl.AddModuleDefinition(modDefInfo);

// Add a control into the module definition
ModuleControlInfo modConCtrl = new ModuleControlInfo();
modConCtrl.ModuleControlID = Null.NullInteger;
modConCtrl.ModuleDefID = modDefId;
modConCtrl.ControlType = SecurityAccessLevel.View;
modConCtrl.ControlSrc = "DesktopModules/Foo/Foo.ascx";
moduleControlController.AddModuleControl(modConCtrl);

If you have ever used Module Definitions under Host menu to create a new module, you should be intimately familiar with these code snippets. For those who can not catch on, we suggest you directly create a new module by yourself. The reason DefaultCacheTime is set to zero is based on the fact the new module must be augmented and debugged; caching a module means you cannot instantly see the change responding to modification of source codes.

EmptyModuleBuilder - DNNModuleCreator

The previous codes about creating and registering a new module is refactored and encapsulated at DNNModuleCreator for reuse. For example, the step to create a new module is encapsulated at DNNModuleCreator.CreateModule() which returns an ID of the created module. Furthermore, we implement a method, CreateAndRegisterModule (Listing 5), to facilitate carrying out all the steps about creating and registering a new module, including generating module files (explained later). The CreateAndRegisterModule can be called by each module builder. Readers do not need to understand too further about “moduleFilesGen.GenerateFiles()” in the current stage. And moduleFilesGen is an instance of IModuleFilesGenerator which should be assigned in the constructor of DNNModuleCreator. The GenerateFiles() will return a list of FileRegInfo which contains the metadata about how to add a module’s controls generated (by GenerateFiles()) into module definitions. We will discuss FileRegInfo in the next sub-section, “EmptyModuleBuilder - Code Generation”.

Listing 5. DNNModuleCreator.CreateModule()

C#
// Constructor
public DnnModuleCreator(IModuleFilesGenerator modFileGen)
{
moduleFilesGen = modFileGen;
}
 
public void CreateAndRegisterModule(string moduleName, string moduleFileName,
     string moduleDescription)
{
     // Generate the module files
     List<FileRegInfo> fileRegInfoList = moduleFilesGen.GenerateFiles();
    
     // create a new module 
int desktopModuleID = this.CreateModule(moduleName, moduleFileName, moduleDescription);
 
// create a module definition
int modDefId = this.AddModuleDefinition(desktopModuleID, moduleName);
 
// add module controls to the module definition
foreach (FileRegInfo info in fileRegInfoList)
{
    AddModuleControl(modDefId, info.Type, info.Key, info.Source, info.Title);
}
}
 
    public int CreateModule(string moduleName, string moduleFileName,
        string moduleDescription)
    {
        DesktopModuleInfo deskModInfo = new DesktopModuleInfo();
 
        deskModInfo.DesktopModuleID = Null.NullInteger;
        deskModInfo.ModuleName = moduleName;
        deskModInfo.FolderName = moduleName;
        deskModInfo.FriendlyName = moduleName;
        deskModInfo.Description = string.IsNullOrEmpty(
            moduleDescription) ? moduleName : moduleDescription;
        deskModInfo.Version = Null.NullString;
 
        int deskModuleId = deskModCtrl.AddDesktopModule(deskModInfo);
 
        return deskModuleId;
    }
 
    // other methods omitted

To further facilitate the reusability DNNModuleCreator, an abstract class named BaseModuleBuilder (at Figure 4), which implements IModuleBuilder (Listing 1), is born. Each module builder can extend BaseModuleBuilder and execute all steps to generate a new module by merely calling BaseModuleBuilder.CreateAndRegisterModule (Listing 6). This method simply delegates the responsibility to DNNModuleCreator.CreateAndRegisterModule (in Listing 5) to do the work only if the abstract properties, such as ModuleName and ModuleFilesGenerator (passed to the constructor of DnnModuleCreator), have been properly implemented in the inheriting classes (e.g., EmptyModuleBuilder discussed currently). In this way, inheriting module builders are not aware of the existence of DNNModuleCreator. Our EmptyModuleBuilder is just one case inheriting BaseModuleBuilder.

Listing 6. BaseModuleBuilder.CreateAndRegisterModule()

C#
public abstract class BaseModuleBuilder : PortalModuleBase, IModuleBuilder 
{
    public abstract IModuleFilesGenerator ModuleFilesGenerator { get;  set; }
 
    public abstract string ModuleName { get; set; }
 
    public abstract string ModuleFileName { get; set; }
 
    private string _moduleDescription;
 
    protected DnnModuleCreator dnnModuleCreator;
 
    public virtual string ModuleDescription
    {
        get { return null; }
        set { _moduleDescription = value; }
    }
 
    protected void CreateAndRegisterModule()
    {
        if(ModuleFilesGenerator == null)
        {
            throw new ArgumentException("ModuleFilesGenerator property cannot be null.");
        }
 
        dnnModuleCreator = new DnnModuleCreator(ModuleFilesGenerator);
        dnnModuleCreator.CreateAndRegisterModule(ModuleName, ModuleFileName, null);
    }
}

Again, let’s review the framework discussed so far, which is illustrated in Figure 8. The key point lies in the cornerstone class, BaseModuleBuilder, which encapsulates all the logic concerning creating and registering modules on DNN. Developers, of course, do not need to inherit BaseModuleBuilder, while they can still reuse DnnModuleCreator when designing their module builders.

image016.jpg
Figure 8. Partial System Architecture

All abstract properties in EmptyModuleBuilder, other than ModuleFilesGenerator, are based on user input, so we implement these properties as in Listing 7, where DnnModuleCreatorUc1 is an instance of DnnModuleCreatorUc, which is only a UserControl (see Figure 9); the DnnModuleCreatorUc1.ModuleName corresponds to TextBox.Text on DnnModuleCreatorUc. Most of EmptyModuleBuilder.ascx is apparently composed of DnnModuleCreatorUc as shown in Figure 9. Because controls on DnnModuleCreatorUc provide the most essential metadata for creating a module, we isolate it as a shared UserControl for other module builders when needed; you can find DnnModuleCreatorUc under ModuleFactory/SharedUc.

Listing 6. Implementing abstract properties in EmptyModuleBuilder

C#
partial class EmptyModulerBuilder : BaseModuleBuilder    
{
 
    public override string ModuleName
    {    
        get { return DnnModuleCreatorUc1.ModuleName; }
        set { DnnModuleCreatorUc1.ModuleName = value; }
    }
 
    public override string ModuleFileName
    {
        get { return DnnModuleCreatorUc1.ModuleFileName; }
        set { DnnModuleCreatorUc1.ModuleFileName = value; }
    }
 
    public override string ModuleDescription
    {
        get { return DnnModuleCreatorUc1.ModuleDescription;}
        set { DnnModuleCreatorUc1.ModuleDescription = value;}
    }
 
}

image018.jpg

Figure 9. DnnModuleCreatorUc in EmptyModuleBuilder

Once users click Submit button in Figure 8, EmptyModuleBuilder first check (at server-side) to see if the module name users afford has existed. The checking logic can be reused in BaseModuleBuilder.HasModuleExisted(). If the module name has not existed in DNN, BaseModuleBuilder.CreateAndRegisterModule() is called to go about all the work, upon which we will elaborate in this section. If you trace the lbtnSubmit_Click to see the logic, you will find the ModuleFilesGenerator (Listing 6) property is assigned to the EmptyModuleFileGenerator, which is under obligation to generate essential codes of a newborn module. Let’s begin to explore it!

EmptyModuleBuilder - Code Generation

The responsibility of code generation is abstracted as a standard-alone interface, IModuleFilesGenerator, shown in Listing 8. The GenerateFiles() is called by DNNModuleCreator.CreateAndRegisterModule (Listing 5) on the fly, and all code generation logic must be implemented within this method. The TargetDirectory specifies the root directory of the module codes generated. In addition, the Language property is only a flag to designate the programming language of the code generated. For the time being, our ModuleFactory merely implements a C# version of module builders where EmptyModuleFileGeneratorCSharp is such an example.

Listing 8. IModuleFilesGenerator

C#
namespace ModuleBuilder.Generator
{
    public interface IModuleFilesGenerator
    {
        /// <summary>
        /// Generate required files of a module
        /// </summary>
        /// <returns>All generated files' registeration information regarding
        /// adding module definitions to a DNN Module</returns>
        List<FileRegInfo> GenerateFiles();
 
        /// <summary>
        /// The driectory where the generated files are placed
        /// </summary>
        string TargetDirectory { get;}
 
 
        /// <summary>
        /// The programming language targeted
        /// </summary>
        EnumLanguage Language { get;}
    }
}

The implementation of EmptyModuleFileGeneratorCSharp is fairly intuitive. We prepare the required template files (.ascx and .ascx.cs) beforehand where we define tokens to be replaced dynamically. The templates for the entry control of an empty module are shown in Listing 9 and 10. Also, templates of empty module’s Settings.ascx and Settings.ascx.cs can be found under ModuleFactory/Templates/CSharp. EmptyModuleFileGenerator’s responsibility is to replace the tokens (confined in #%...%#) in template files, and put the module files generated in place (i.e., under TargetDirectory ).

Listing 9. EmptyModule.ascx.txt

C#
<%@ Control Language="C#" AutoEventWireup="true" CodeFile="#%ModuleFileName%#.ascx.cs"
    Inherits="#%ModuleFileName%#" %>

Listing 10. EmptyModule.ascx.cs.txt

C#
public partial class #%ModuleFileName%# : PortalModuleBase
{
    protected void Page_Load(object sender, EventArgs e)
    {
 
    }
}

A thoughtful design consideration here is to separate template-access from EmptyModuleFileGeneratorCSharp. We would be better off in maintaining codes if the template-access logic is abstracted out of EmptyModuleFileGeneratorCSharp (i.e., Orthogonal Desgin). Imagine the disaster scenario where you needed to change the directory to sorting template files, and the file path resolving logic was creeping into every module file generators, not only EmptyModuleFileGeneratorCSharp. Hence, we design EmptyModuleFileGenerator to accompany a template-access object, called EmptyModuleFileGeneratorDAOCSharp to handle reading template and writing codes generated (to specified TargetDirectory); so do other module file generators in our ModuleFactory.

The architecture discussed in this section so far can, in a word, be illustrated as Figure 10: EmptyModuleBuilder uses EmptyModuleFileGeneratorCSharp to generate code files. However, recall that it is the inheriting method, BaseModuleBuilder.CreateAndRegisterModule (Listing 6), will pass EmptyModuleFileGeneratorCSharp into the constructor of DnnModuleCreator that in turn uses EmptyModuleFileGeneratorCSharp to generates code files.

image020.jpg
Figure 10. Architecture of EmptyModuleBuilder and EmptyModuleFileGeneratorCSharp

Take a look at the key method, EmptyModuleFileGeneratorCSharp.GenerateFiles(), shown at Listing 11 for understanding nuts and bolts of the code generation process. The entry control’s code templates are pulled out with EmptyModuleFileGeneratorDAOCSharp.EntryAscx (i.e., at ModuleFactory/Templates/CSharp EmptyModuleSettings.ascx.cs.txt). And then, the template content is replaced with the token-replacement algorithm that is trivially implemented at private methods, replaceAscxTempTokens(), and replaceCodeBehindTempTokens(). Roughly stated, String.Replace(string oldValue, string newValue) is employed to replace tokens with user inputs (e.g., module name, module file name in Figure 5). After tokens are placed, module files are written via EmptyModuleFileGeneratorDAOCSharp.WriteToTargetDirecotry(). Finally, the required FileRegInfo objects are populated, returning to DNNModuleCreator to go about the process of registering controls to a DNN module shown in the for-loop of Listing 5. FileRegInfo encapsulates the metadata for adding a control (i.e., UserControl) to a DNN’s module definition. Part of FileRegInfo is shown on Listing 12.

Listing 11. EmptyModuleFileGeneratorCSharp.GenerateFiles()

C#
public List<FileRegInfo> GenerateFiles()
{
    EmptyModuleFileGeneratorDAOCSharp emptyDAOCSharp =
        new EmptyModuleFileGeneratorDAOCSharp(TargetDirectory);
    // Replace tokens in template files
    emptyDAOCSharp.EntryAscx = replaceAscxTempTokens(emptyDAOCSharp.EntryAscx);
    emptyDAOCSharp.EntryCodeBehind = replaceCodeBehindTempTokens(
        emptyDAOCSharp.EntryCodeBehind);
 
    // Specify output file names
    emptyDAOCSharp.EntryAscxFileName = ModuleFileName + ".ascx";
    emptyDAOCSharp.EntryCodeBehindFileName = ModuleFileName + ".ascx.cs";
 
    // generate result codes to target directory
    emptyDAOCSharp.WriteToTargetDirecotry();
 
    // Meatadata for DnnModuleCreator to create and register modules to DNN
    List<FileRegInfo> fileRegInfoList = new List<FileRegInfo>();
 
    // Path used to register entry view
    string dnnModuleRegPath = "DesktopModules/" + ModuleName + "/" +
        emptyDAOCSharp.EntryAscxFileName;
    fileRegInfoList.Add(new FileRegInfo(dnnModuleRegPath, "",
        SecurityAccessLevel.View, ""));
 
    string settingsFilePath = "DesktopModules/" + ModuleName + "/" +
        emptyDAOCSharp.SettingsAscxFileName;
    fileRegInfoList.Add(new FileRegInfo(settingsFilePath, "Settings",
        SecurityAccessLevel.View, ModuleName + "Settings"));
    return fileRegInfoList;
}

Listing 12. Code snippet of FileRegInfo

C#
namespace ModuleBuilder.Generator
{
    public class FileRegInfo
    {
        /// <summary>
        /// File path starting at DesktopModules. E.g.,
        /// DesktopModules\module1\module1.ascx
        /// </summary>
        public string Source
        {
            get { return _source; }
            set { _source = value; }
        }
 
        private string _title;
        public string Title
        {
            get { return _title; }
            set { _title = value; }
        }
    }
 
}

We have completed the discussion about EmptyModuleBuilder. Recall in the section, “Design of Loading Module Builders Dynamically”, we promise you can plug new module builder into ModuleFactory. Therefore, we demonstrate how to plug a GridModuleBuilder in the next section.

Plug a GridModuleBuilder into ModuleFactory

The goal of GridModuleBuilder is to generate a DNN module where a Telerik’s RadGrid resides to let users view or modify data retrieving from databases. GridModuleBuilder can generate the repetitive codes for initiating select/insert/update/delete operations on single table, provided select SQL is given. To simplify the demonstration, we only let users specify select SQL statement without any parameters. You can preview the GridModuleBuilder implemented next in Figure 11.

To leverage the framework of ModuleFactory as in EmptyModuleBuilder, you let GridModuleBuilder.ascx.cs inherit abstract BaseModuleBuilder, which provides default implementation of IModuleBuilder (Listing 1). Then, you implement abstract properties of ModuleName and ModuleFileName as we do for EmptyModuleBuilder: reusing DnnModuleCreatorUc.ascx (Listing 7). In addition, you need to provide a TextBox and a DropDownList, letting users enter select SQL statement and select connection strings in web.config. We put these two controls on a separate UserControl called SQLUc.ascx, and use RadMultiPage to implement wizard-like user interface. The RadMultiPage markup in GridModuleBuilder.ascx is shown in Listing 13. If you are interested in details, please refer to GridModuleBuilder.ascx and GridModuleBuilder.ascx.cs. Now, you can add

<add friendlyname="Grid module builder"
    source="GridModuleBuilder/GridModuleBuilder.ascx" />

to ModuleFactory.config in Listing 2 to plug GridModuleBuilder into ModuleFactory and view the appearance of it (Figure 11). Having setup the user interface, let’s describe implementation of the abstract ModuleFilesGenerator property (with GridModuleFileGeneratorCSharp) next.

Listing 13. RadMultiPage Markup in GridModuleBuilder.ascx

HTML
<telerik:RadMultiPage ID="RadMultiPage1" runat="server" SelectedIndex="0">
<telerik:RadPageView ID="pageViewModule" runat="server">
  <uc1:DnnModuleCreatorUc ID="DnnModuleCreatorUc1" runat="server" />
<div style="text-align: center">
<asp:LinkButton ID="lbtnNext1" CssClass="CommandButton" runat="server"
    OnClick="lbtnNext1_Click">Next</asp:LinkButton>
</div>
</telerik:RadPageView>
<telerik:RadPageView ID="pageViewSqlParams" runat="server">
<uc2:SQLUc ID="SQLUc1" runat="server" />
            <div style="text-align: center">
      <asp:LinkButton ID="lbtnPre2" runat="server" CssClass="CommandButton"
          OnClick="lbtnPre2_Click">Previous</asp:LinkButton>   
<asp:LinkButton ID="lbtnSubmit" OnClick="cmdSubmit_Click" runat="server"
    CssClass="CommandButton" CausesValidation="true"
    BorderStyle="none" Text="Submit"></asp:LinkButton>
</div>
</telerik:RadPageView>
</telerik:RadMultiPage>

image022.jpg

Figure 11. GridModuleBuilder

We implement the abstract GridModuleBuilder.ModuleFilesGenerator property with GridModuleFileGeneratorCSharp. The code generation technique is the same as EmptyModuleFileGeneratorCSharp: prepare template files and replace tokens within it with users input (e.g., select SQL). For details, please see the aforementioned “EmptyModuleBuilder - Code Generation” section. If you take a look at RadGridTemplateModule.ascx.cs.txt (within Templates/CSharp), you should feel the power of code generators. There are pretty repetitive codes you need to copy and paste if you start from an empty module.

Conclusion

In this article, we describe the benefits of code generators, and provide a framework, called ModuleFactory, to realize a code generator on DNN platform. We go into the nuts and bolts of ModuleFactory where two concrete code generators, EmptyModuleBuilder and GridModuleBuilder, are implemented to demonstrate the feasibility of the idea. You might organize the domain-specific patterns in your application domain, evaluating if it is worth writing module builders. Also, we show you how to plug new module builder into ModuleFactory without modifying existing codes, which is also a main theme promoted in this article.

Future Works

Here are three points our team is still engaged in.

  • First, the GridModuleBuilder simply demonstrates two users-specified parameters: “select SQL” and “connection string”. You can imagine there are many things that can be parameterized. More importantly, you should generate codes of GridModuleBuilder that call data access layers (DALs) you create in your team, rather than entangle codes regarding DAL with codes of business layers or presentation layers as we do in GridModuleBuilder. My team has been engaged in binding our DAL in GridModuleBuilder; we would like to share it with the public when the mechanism is full-fledged.
  • Secondly, we are implementing a runtime editor of controls’s properties. The purpose of this editor is to let non-programmers (e.g., end users) can also set properties (e.g., Label.Visible=false) directly via browsers instead of going into the clutter of codes. The idea behind this editor is kin to this article, but the implementation is more complicated for web application. The codes for employing this editor can be automatically generated by the module builder. In this way, all our company DNN modules are inherently armed with this feature.
  • Thirdly, we are studying a mechanism that can prevent users form altering codes we generate when they need to modify module codes to add more or improve existing features. Hence, developers might re-generate codes using module builders without covering the codes they write by themselves.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)