Rainbow Portal: Design and Implementation

DUEMETRI

March 2003

Summary: This article describes the design and architectural decisions for the Rainbow Portal application. In addition, a detailed examination and explanation of the code required to extend it is also covered. (2517 printed pages)

Overview

What is the Rainbow Portal?

The Rainbow Portal is based on Microsoft’s IBuySpy Portal Solution Kit which demonstrates how you can use ASP.NET along with the Microsoft .NET Framework to build intranet and Internet portal applications. The sample shows off many key features available with ASP.NET and also provides a “best practices” application that developers can use as a base to build their own ASP.NET applications.

The Rainbow Portal demonstrates many features offered by the ASP.NET technology including:

·         Multiple portals managed by the same code and storing data on the same database

·         Cross-browser support for Netscape and Internet Explorer

·         Wide support for different cultures and custom regional settings

·         Mobile device support for WAP/WML and Pocket Browser devices

·         Clean code/html content separation using server controls

A number of classes that extend the ASP.NET objects and provide a consistent method for developing custom pages and custom controls

·         Pages that are constructed from dynamically-loaded user controls

·         Extensive use of themes and layouts providing tools for customizing the portal design

·         Configurable output caching of portal page regions and Portal Settings

·         Multi-tier application architecture

·         ADO.NET data access using SQL stored procedures

·         Windows authentication - username/password in Active DS or NT SAM

·         Forms authentication using a database for usernames/passwords

·         Role-based security to control user access to portal content

This white paper discusses the Rainbow Portal application in depth and provides insight from the perspective of the creators. In addition, the article covers how the Rainbow Portal can be used as a template for building online portals by examining many of the key application features and the technology used to implement them.

The original  IBuySpy Portal is distributed in two variants, one version written directly to the .NET framework SDK and the second version written using Microsoft ® Visual Studio.NET™. The main difference between the two is that the latter uses code-behind. Both variants contain versions written in C#.NET and VB.NET. The Rainbow Portal is based on the VS.NET C# version.

Application Architecture

The Rainbow Portal uses multi-tier application architecture.  The main data source for the application is a SQL Server database along with stored procedures.  The data access is provided through a Microsoft .NET assembly that provides access to the data source via the stored procedures.  In addition, the portal framework is built through the use of a number of assemblies that handle the security and configuration of the portal.  Web Forms and user controls make up the presentation layer and handle the display and management of the portal data for the user.

Database

All of the configuration settings for portals are stored in a SQL Server database. This allows server administrators to farm the front-end of the portal across a number of servers each pulling from a single unique data store. This section provides an overview of the database used in the Rainbow Portal.

Database Schema

The Rainbow Portal database schema is shown in Figure 1Figure 1.

Figure 1. Physical Database Schema

Portal data tables

Rainbow stores the portals structure in a hierarchical set of tables.

At the top we have the Portal, each portal having its own unique design and custom properties, saved in the PortalSettings table in a Key/Value list.

Each portal contains Tabs. Tabs can be considered the “Pages” of the portal. Tabs can be nested in a tree like structure. The TabSettings table stores custom data for each Tab.

Tabs contain modules. Each module is an object that can handle a well defined set of data. There are modules for managing a list of Links, HTML text, Contacts and more.

The GeneralModuleDefinitions table contains a list of all available modules. Modules can be activated on Portal basis. The ModuleDefinitions table contains a list of modules associated with a single portal.

The Module table contains a list of module instances: where the module is placed (Tab) and permissions associated with the module itself. ModuleSettings table stores custom data for each Module.

Stored Procedures

The Rainbow Portal uses stored procedures to encapsulate all of the database queries. Stored procedures provide a clean separation between the database and the middle-tier data access layer. This in turn provides easier maintenance, since changes to the database schema will be invisible to the data access components. Using stored procedures also provide performance benefits since they are optimized the first time they are run and then retained in memory for subsequent calls. 

The Portal Framework

The Rainbow Portal contains an extensible framework that allows users to build and use individual portal modules to handle the display and management of data. The following sections will cover the basics of the portal framework, as well as how it was built.

Portal Settings

The portal settings are represented by the PortalSettings Class, which is defined in the Configuration namespace component. 

These settings include the following:

·         The Portal ID

·         The Portal Name

·         The Desktop Tabs Collection

·         The Mobile Tabs Collection

·         Portal Custom Settings

·         Active Tab and its Custom Settings

·         Consistent access to Portal and site properties

·         Active Theme and Layout

The PortalSettings class is updated and placed into the “Context” object upon each web request of the portal application. This is achieved by using the Application_BeginRequest event in the Global.asax.cs file. The parameters passed are the current tab id (the current “Page”) and the Alias of the current Portal.

settings = new PortalSettings(tabId, portalAlias);

Once stored in the Context object, these settings can be obtained from anywhere in the application including all pages, components, and controls by accessing the Context item with the name “PortalSettings”. You can access current portal settings using the portalSettings property defined on Rainbow.UI.Page and Rainbow.UI.PortalModuleControl

Portal Tabs

The Tabs are stored in two public fields of the PortalSettings object we described in the previous section.  The fields, DesktopTabs and MobileTabs, are of the type ArrayList and contain instances of the TabStripDetails class, which represents an individual tab.  Access to the tabs collection is achieved through the PortalSettings Context item.

The display of the tabs (i.e. the top-level navigation) is handled by the Rainbow Layouts, usually in the DesktopPortalBanner.ascx user control.

Rainbow provides a set of controls, located in the Rainbow.UI.WebControls namespace, that can be used to build easily a custom page.

  • HeaderImage
  • HeaderMenu
  • HeaderTitle
  • DesktopNavigation
  • MenuNavigation
  • BreadCrumbs
  • DesktopPanes
  • Paging

 

DesktopNavigation and MenuNavigation controls iterate through the tabs collection checking whether the current user has rights to view the tab.  If the necessary role is met, the tab is added to another collection that will be bound to a DataList.

When a user clicks on a given tab link, the PortalSettings object is updated to reflect the new ActiveTab. When the DesktopPortalBanner.ascx is reloaded, the item in the DataList that corresponds to the active tab’s index is set to the DataList’s SelectedIndex property.

Portal Modules

Portal Modules provide the actual content of a Rainbow Portal. The modules are user controls that inherit the PortalModuleControl base class, which provides the necessary communication between the modules and the underlying Portal Framework.

The IBuySpy Portal comes with 18 built-in portal modules that are available “out of the box” for displaying custom content and 8 modules delegated to Administrative functions.

Portal Security

The security design in the portal makes use of both authentication and authorization. Authentication is the process by which the application verifies a user’s identity and credentials. Authorization then verifies the authenticated user’s permissions for a particular requested resource.

The portal supports both forms based and windows based authentication. The authentication mode is defined in the web.config and the User.Identity.Name property maintains the user name. Forms based authentication stores the usernames and passwords in the database and Windows authentication uses a domain/active directory with the NTLM  challenge/response protocol. The authorization for the portal is handled using role based security to determine whether or not a user has access to a particular resource. Users are grouped into various roles (admins, power users, devs, etc.) and the role mappings are stored in the database. Rainbow also defines special roles like “Authenticated Users” and “Unauthenticated Users” that are internally managed.

The tabs and modules in the portal maintain access control lists (ACL) to determine who has permission to access the control. This prevents a normal user accessing the administration functionality.

Rainbow extends the original IBuySpy Edit permission, adding View, Add, Delete and Edit Properties permissions. Special permissions are also available for Workflow Support.

Security checks are delegated to bases classes in Rainbow so a Module developer can safely ignore tests for authorized roles.

The current user’s role mappings are set for the request in Global.asax in the Application_AuthenticateRequest() event. The Context.User is then set using the GenericPrincipal method and the User.IsInRole method can be used to verify whether the current user is in a specific role.

The database calls for all of the role-based checks are contained in Security namespace classes.

Administering the Rainbow Portal

The Rainbow Portal has an online administration tool that allows users in the “Admins” role to manage the security, layout, and content of the portal.  Users that are logged in that belong to the “Admins” role will see an “Admin” tab link that takes them to the administration tool.

The portal administration allows the user to perform a variety of site management and configuration tasks. This is the place where new tabs (pages) can be added, modules can be added to them, security roles defined, etc.

 

Extending the Rainbow Portal: a tutorial

The Rainbow Portal was built with the idea of extensibility in mind, providing a way for developers to easily add portal modules that can “plug” into the framework. In this section we will look at the steps that you can follow to build your own portal modules. To do this, we will build a Milestones portal module that will display project milestones.

Extending the Data Layer

Most of the portal modules use the portal database as their primary data store. We will do the same for our example. Therefore the first step is to extend the data layer.  We begin by creating a new Table called Milestones, as shown in Figure 2Figure 2.

Figure 2.  Milestones Table

Create a relationship with the Module table:

Next, we will create the stored procedures required to handle access to the Milestones table.

Rainbow provides a tool for doing this, please download it at:

http://www.rainbowportal.net/Rainbow/_Rainbow/Documents/CodeWizard.zip

This tool will do two things: 

·         Will create the stored procedures.

·         Will create the C# data layer for accessing procedures.

Launch the CodeWizard.

Select your server and authentication.

Select Database name and then the table to generate.

Press the Generate button.

Click on “Copy SP” then paste and execute the script in SQL Query Analayzer; you will get all the procedures you need.

Click on “Copy C#”, open the Visual Studio project and add a new MilestonesDB.cs file in DesktopComponents folder, then paste the code there.

This way you have created the data access layer (DAL) component to provide access to the Milestone procedures.

Obviously you can do this task by hand or use CodeWizard as a “first step” tool and refine you code by hand later. If you have DAL ready from old IBS modules there is no need to change this.

Creating the User Control

After the database is taken care of, the next step is to create the user control that will handle the milestone user interface.

The user control will contain a DataGrid that will define three columns: Title, Completion Date, and Status.

In this tutorial we use Visual Studio .Net.

Go to DesktopModules and add a new User Control called “Milestones.ascx”.

Add a DataGrid control on the Milestones Control page. Name it “myDataGrid”.

Milestones.ascx

Here is the front part of the control. It goes in the “Milestones.ascx” file.

 

<%@ Control Language="c#" AutoEventWireup="false" Codebehind="Milestones.ascx.cs" Inherits="Rainbow.DesktopModules.Milestones"%>

<%@ Register TagPrefix="tra" Namespace="Rainbow.UI.WebControls.Globalized" Assembly="Rainbow" %>

<asp:DataGrid id="myDataGrid" HeaderStyle-CssClass="Normal" HeaderStyle-Font-Bold="true" ItemStyle-CssClass="Normal" AutoGenerateColumns="false" CellPadding="5" BorderWidth="0" Width="100%" EnableViewState="false" runat="server">

    <Columns>

        <asp:TemplateColumn>

            <ItemTemplate>

                <tra:HyperLink id="editLink" TextKey="EDIT" Text="Edit" ImageUrl="~/images/edit.gif" NavigateUrl='<%# Rainbow.HttpUrlBuilder.BuildUrl("~/DesktopModules/MilestonesEdit.aspx", "ItemID=" + DataBinder.Eval(Container.DataItem,"ItemID") + "&Mid=" + ModuleId) %>' Visible="<%# IsEditable %>" runat="server" />

            </ItemTemplate>

        </asp:TemplateColumn>

        <tra:BoundColumn DataField="Title" TextKey="MILESTONE_TITLE" HeaderText="Title" runat="server" />

        <tra:BoundColumn DataField="EstCompleteDate" TextKey="MILESTONE_COMPL_DATE" HeaderText="Compl. Date" runat="server" DataFormatString="{0:d}" />

        <tra:BoundColumn DataField="Status" TextKey="MILESTONE_STATUS" HeaderText="Status" runat="server" />

    </Columns>

</asp:DataGrid>

Add support for localization

Rainbow includes great support for localizing pages. It requires no effort by the module developer and provides a consistent and powerful framework for localization.

There are several WebControls in the Rainbow.UI.WebControls.Globalized namespace that inherit directly from standard .Net WebControls.

First you have to register the Globalized control namespace at top of your page.

 

<%@ Register TagPrefix="tra" Namespace="Rainbow.UI.WebControls.Globalized"

Assembly="Rainbow" %>

 

Then add your controls using the “tra:” prefix instead of “asp:” this way:

 

<tra:Literal TextKey="MILESTONE_DETAIL" Text="Milestones Details" runat="server"></tra:Literal>

The TextKey property contains the Key used by the Localize Class for providing translated text at runtime. Be aware that this key must be unique across a rainbow installation, so if you are doing a particular module you should prefix names in some way to avoid possible conflicts.

It is important to define the Text property too (or the property that contains the Text of the control, for BoundColumn it will be HeaderText). The text value is used as a default translation for English language and a base for translating the Key into all supported languages. This can be really useful in case of a Key conflict because it can provide a default translation when you change the Key.

If a key of the same name is already used no error occurs but you will most likely get a wrong translation.

To track the key usage, look at the Sections table.

If you need to enter pure text please use the Literal control. Avoid the use of ASP inner script (<% - %> blocks like <%= Localize.GetString("MY_KEY")%>) because usage cannot be tracked.

If you need to get a key translation from code use this syntax:

Localize.GetString("MY_KEY", “My translation”, control).

The control should be the control on which you will use the translation. This can be a UserControl, a WebControl or a Page. If control is not applicable pass the page itself: each control has a Page reference in it.

Using the simple syntax also works but it is not recommended: Localize.GetString("MY_KEY”).

Milestones.ascx.cs

Show the Milestones.ascx.cs code page and copy there the code below. I have put comments in it. Read it carefully.

 

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

using System.Data.SqlClient;

using Rainbow.Admin;

 

namespace Rainbow.DesktopModules

{

    /// <summary>

    /// IBS Portal Milestone Module - Edit page part

    /// Writen by: Elaine Ossipov  - 9/11/2002 - admin@sbsc.net

    /// Moved into Rainbow by Jakob Hansen, hansen3000@hotmail.com

    /// Updated by Manu as Rainbow Tutorial

    /// </summary>

    public class MilestonesEdit : Rainbow.UI.EditItemPage

    {

        protected Rainbow.UI.WebControls.Globalized.Literal Literal1;

        protected Rainbow.UI.WebControls.Globalized.Literal Literal2;

        protected System.Web.UI.WebControls.TextBox TitleField;

        protected Rainbow.UI.WebControls.Globalized.RequiredFieldValidator Req1;

        protected Rainbow.UI.WebControls.Globalized.Literal Literal3;

        protected System.Web.UI.WebControls.TextBox EstCompleteDate;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req2;

        protected System.Web.UI.WebControls.CompareValidator VerifyCompleteDate;

        protected Rainbow.UI.WebControls.Globalized.Literal Literal4;

        protected System.Web.UI.WebControls.TextBox StatusBox;

        protected Rainbow.UI.WebControls.Globalized.RequiredFieldValidator Req3;

        protected System.Web.UI.WebControls.Label CreatedBy;

        protected System.Web.UI.WebControls.Label CreatedDate;

       

        private void Page_Load(object sender, System.EventArgs e)

        {

            // If the page is being requested the first time, determine if a

            // Milestone itemId value is specified, and if so,

            // populate the page contents with the Milestone details.

            if (Page.IsPostBack == false)

            {

                //Item id is defined in base class

                if (ItemId > 0)

 

            {

                    //Obtain a single row of Milestone information.

                    MilestonesDB milestonesDB = new MilestonesDB();

                    SqlDataReader dr = milestonesDB.GetSingleMilestones(ItemId);

 

                    //Load the first row into the DataReader

                    dr.Read();

                    TitleField.Text = (String) dr["Title"];

                    EstCompleteDate.Text = ((DateTime) dr["EstCompleteDate"]).ToShortDateString();

                    StatusBox.Text = (String) dr["Status"];

                    CreatedBy.Text = (String) dr["CreatedByUser"];

                    CreatedDate.Text = ((DateTime) dr["CreatedDate"]).ToShortDateString();

                    dr.Close();

                }

                else

                {

                    //Provide defaults

                    EstCompleteDate.Text = DateTime.Now.AddDays(60).ToShortDateString();

                }

            }

        }

 

        /// <summary>

        /// This procedure is automaticall

        /// called on Update

        /// </summary>

        protected override void OnUpdate(EventArgs e)

        {

            // Calling base we check if the user has rights on updating

            base.OnUpdate(e);

 

            // Update onlyif the entered data is Valid

            if (Page.IsValid == true)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                if (ItemId <= 0)

                    milestonesDb.AddMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

                else

                    milestonesDb.UpdateMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

 

                // Redirects to the referring page

                // This method is provided by the base class

                this.RedirectBackToReferringPage();

            }

        }

 

        /// <summary>

        /// This procedure is automaticall

        /// called on Update

        /// </summary>

        override protected void OnDelete(EventArgs e)

        {

            // Calling base we check if the user has rights on deleting

            base.OnUpdate(e);

 

            if (ItemId > 0)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                milestonesDb.DeleteMilestones(ItemId);

            }

 

            // This method is provided by the base class

            this.RedirectBackToReferringPage();

        }

       

        #region Web Form Designer generated code

        /// <summary>

        /// Raises OnInitEvent

        /// </summary>

        /// <param name="e"></param>

        protected override void OnInit(EventArgs e)

        {

            InitializeComponent();

            base.OnInit(e);

        }

 

        /// <summary>

        /// Required method for Designer support - do not modify

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()

        {   

            this.Load += new System.EventHandler(this.Page_Load);

 

        }

        #endregion

   

    }

}

Generate the guid

Each Rainbow Module must define its own internal Guid. This guid must be unique. To generate a guid use the GuidGen tool on Visual Studio’s Tools Menu.

Inherit the Rainbow Module Control Base Class

All portal modules are actually no more than simple ASP.NET user controls that inherit the PortalModuleControl base class. This base class provides all the hooks that are required for the portal module to interact with the framework.

Add the Module Title

One of the user controls that is available to portal modules is the Title user control. This control will generate the appropriate HTML for the Module’s title.

The title is added in the OnInit procedure.

Implement search support

Rainbow defines a framework for enable search on each module.

Enabling search on you module is an easy three steps procedure.

Implementing search on your modules

1.      You have to remove abstract word from class definition.
Visual Studio automatically defines Codebehind pages as abstracts, anyway searchable modules must not be abstract classes because are instantiated using reflection.

2. Override the search method.

/// <summary>

/// Searchable module implementation

/// </summary>

/// <param name="portalID">The portal ID</param>

/// <param name="userID">Id of the user is searching</param>

/// <param name="searchString">The text to search</param>

/// <param name="searchField">The fields where perfoming the search</param>

/// <returns>The SELECT sql to perform a search on the current module</returns>

public override string SearchSqlSelect(int portalID, int userID, string searchString, string searchField)

{

// Parameters:

// Table Name: the table that holds the data

// Title field: the field that contains the title for result, must be a field in the table

// Abstract field: the field that contains the text for result, must be a field in the table

// Search field: pass the searchField parater you recieve.

 

Rainbow.Helpers.SearchDefinition s = new Rainbow.Helpers.SearchDefinition("Milestones", "Title", "Status", "CreatedByUser", "CreatedDate", searchField);

        

//Add here extra search fields, this way

//s.ArrSearchFields.Add("itm.ExtraFieldToSearch");

 

// Builds and returns the SELECT query

return s.SearchSqlSelect(portalID, userID, searchString);

}

3. Override Searchable property

/// <summary>

/// If the module is searchable you

/// must override the property to return true

/// </summary>

public override bool Searchable

{

    get

    {

        return true;

    }

}

Add Support for an Edit Page

In order to allow users to edit and add additional Milestones, support for an edit page must be added.

This support is added by defining EditUrl to the Module Title Control, if you have a page that edits the content of your module directly, like HTML module; or defining AddUrl, if you have a page for adding items to the control (like this Milestones Module).

An optional EditText or AddText can be defined. This text is a localization Key and will be localized.

The naming convention starts with ModuleName and then adds the function.

e.g. MilestonesEdit

Create the Edit Page

Once we have added support for an edit page using the Title User Control, we need to create the actual edit page. In addition to the HTML, we will define four methods:

·         Page_Load

·         OnUpdate

·         OnDelete

·         OnCancel (already defined in base class)

 

using System;

 

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

using System.Data.SqlClient;

using Rainbow.Admin;

 

namespace Rainbow.DesktopModules

{

    /// <summary>

    /// IBS Portal Milestone Module - Edit page part

    /// Writen by: Elaine Ossipov  - 9/11/2002 - admin@sbsc.net

    /// Moved into Rainbow by Jakob Hansen, hansen3000@hotmail.com

    /// Updated by Manu as Rainbow Tutorial

    /// </summary>

    public class MilestonesEdit : Rainbow.UI.EditItemPage

    {

        protected System.Web.UI.WebControls.TextBox TitleField;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req1;

        protected System.Web.UI.WebControls.TextBox EstCompleteDate;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req2;

        protected System.Web.UI.WebControls.CompareValidator VerifyCompleteDate;

        protected System.Web.UI.WebControls.TextBox StatusBox;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req3;

        protected System.Web.UI.WebControls.Label CreatedBy;

        protected System.Web.UI.WebControls.Label CreatedDate;

       

        private void Page_Load(object sender, System.EventArgs e)

        {

            // If the page is being requested the first time, determine if a

            // Milestone itemId value is specified, and if so,

            // populate the page contents with the Milestone details.

            if (Page.IsPostBack == false)

            {

                //Item id is defined in base class

                if (ItemId > 0)

                {

                    //Obtain a single row of Milestone information.

                    MilestonesDB milestonesDB = new MilestonesDB();

                    SqlDataReader dr = milestonesDB.GetSingleMilestones(ItemId);

 

                    //Load the first row into the DataReader

                    dr.Read();

                    TitleField.Text = (String) dr["Title"];

                    EstCompleteDate.Text = ((DateTime) dr["EstCompleteDate"]).ToShortDateString();

                    StatusBox.Text = (String) dr["Status"];

                    CreatedBy.Text = (String) dr["CreatedByUser"];

                    CreatedDate.Text = ((DateTime) dr["CreatedDate"]).ToShortDateString();

                    dr.Close();

                }

            }

        }

 

        /// <summary>

        /// This procedure is automatically

        /// called on Update

        /// </summary>

        override protected void OnUpdate()

        {

            // Calling base we check if the user has rights on updating

            base.OnUpdate();

 

            // Update onlyif the entered data is Valid

            if (Page.IsValid == true)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                if (ItemId <= 0)

                    milestonesDb.AddMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

                else

                    milestonesDb.UpdateMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

 

                // Redirects to the referring page

                // This method is provided by the base class

                this.RedirectBackToReferringPage();

            }

        }

 

        /// <summary>

        /// This procedure is automatically

        /// called on Update

        /// </summary>

        override protected void OnDelete()

        {

            // Calling base we check if the user has rights on updating

            base.OnUpdate();

 

            if (ItemId > 0)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                milestonesDb.DeleteMilestones(ItemId);

            }

 

            // This method is provided by the base class

            this.RedirectBackToReferringPage();

        }

       

        #region Web Form Designer generated code

        /// <summary>

        /// Raises OnInitEvent

        /// </summary>

        /// <param name="e"></param>

        protected override void OnInit(EventArgs e)

        {

            InitializeComponent();

            base.OnInit(e);

        }

 

        /// <summary>

        /// Required method for Designer support - do not modify

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()

        {   

            this.Load += new System.EventHandler(this.Page_Load);

        }

        #endregion

   

    }

}

Workflow

Rainbow defines a powerful way for implementing workflow on custom modules.

 

How workflow works

1.      The editor switches to the staging view and edit'sedits the content

2.      The editor has finished editing it's content so he clicks the "Request approval button”

3.      Then he's shown a page where he can (not must) send an email to the guy who has to approve to new content.

4.      The approval guy get's it's email and navigates opens his browser and navigates to the specific module the approval guy has two option's: approve & reject. When he clicks the approve button he's shown a page where he can (not must)  send an email to the guy who has to publish the new content.

Add Workflow support to you module

Workflow at the moment work’s only for modules which persist theirthere content in the Rainbow database.

The table in which the content is persisted must be linked through a foreign key to the modules table. If this table contains child tables itself then the workflow will fail. I can support this too, but then I have to make the “publish” stored procedure recursive.

 

For adding workflow support to your module do the following steps.

 

Database modifications

1)     Create an identical table in the database to the table in which you store the modules content. The table must have the same name as the original with “st_” prefix.

2)     Modify your stored procedure so it updates the staging table instead of the “production” table.

3)     Add a trigger to the staging table. For example see below or to the trigger on the dbo.st_HtmlText table.

 

/* Trigger on staging table */

CREATE TRIGGER [st_MilestonesModified]

ON [st_Milestones]

FOR DELETE, INSERT, UPDATE

AS

BEGIN

DECLARE ChangedModules CURSOR FOR

     SELECT ModuleID

     FROM inserted

     UNION

     SELECT ModuleID

     FROM deleted

 

DECLARE @ModID  int

 

OPEN ChangedModules

 

FETCH NEXT FROM ChangedModules

INTO @ModID

 

WHILE @@FETCH_STATUS = 0

BEGIN

     EXEC ModuleEdited @ModID

 

     FETCH NEXT FROM ChangedModules

     INTO @ModID

END

 

CLOSE ChangedModules

DEALLOCATE ChangedModules

 

END

 

4)     Add a parameter to the stored procedure you use for retrieving the content to indicate if you want the staging data or the production data. For example see below or dbo.GetHtmlText

 

/* Procedure GetMilestones*/

CREATE PROCEDURE GetMilestones

@ModuleId int,

@WorkflowVersion int

AS

 

IF ( @WorkflowVersion = 1 )

SELECT

     ItemId,

     ModuleId,

     CreatedByUser,

     CreatedDate,

     Title,

     EstCompleteDate,

     Status

FROM

     Milestones

WHERE

     ModuleId = @ModuleId

ELSE

SELECT

     ItemId,

     ModuleId,

     CreatedByUser,

     CreatedDate,

     Title,

     EstCompleteDate,

     Status

 

FROM

     st_Milestones

WHERE

     ModuleId = @ModuleId

 

 

C# source code modifications

1)     Add a parameter to indicate the version you want to retrieve to the retrieve function in your DB component corresponding to your module

2)     In the constructor of your module set the “SupportsWorkflow” property = true;

3)     In the databinding procedure of your module use the “Version” property of the PortalModuleControl with the retrieving procedure of your DB component corresponding to your module

4)     In the databinding of the edit page of your module always use WorkflowVersion.Staging, because you always have to bind your editing env to the staging environment.

MilestonesDB.cs

Show the MilestonesDB.cs code page and copy there the code below.

using System;

using System.Configuration;

using System.Data;

using System.Data.SqlClient;

 

using Rainbow.Configuration;

 

namespace Rainbow.DesktopModules

{

    public class MilestonesDB

    {

        /// <summary>

        /// GetSingleMilestones

        /// </summary>

        /// <param name="ItemId">ItemId</param>

        /// <param name="version">version</param>

        /// <returns>A SqlDataReader</returns>

       

        // add version parameter

        public SqlDataReader GetSingleMilestones(int ItemId, WorkFlowVersion version)

        {

            // Create Instance of Connection and Command Object

            SqlConnection myConnection = PortalSettings.SqlConnectionString;

            SqlCommand myCommand = new SqlCommand("GetSingleMilestones", myConnection);

 

            // Mark the Command as a SPROC

            myCommand.CommandType = CommandType.StoredProcedure;

 

            // Add Parameters to SPROC

            SqlParameter parameterItemId = new SqlParameter("@ItemId", SqlDbType.Int);

            parameterItemId.Value = ItemId;

            myCommand.Parameters.Add(parameterItemId);

 

            // add version parameter to command

            SqlParameter parameterWorkflowVersion = new SqlParameter("@WorkflowVersion", SqlDbType.Int, 4);

            parameterWorkflowVersion.Value = (int)version;

            myCommand.Parameters.Add(parameterWorkflowVersion);

 

            // Execute the command

            myConnection.Open();

            SqlDataReader result = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

 

            // Return the datareader

            return result;

        }

 

 

        /// <summary>

        /// GetMilestones

        /// </summary>

        /// <param name="ItemId">ItemId</param>

        /// <param name="version">version</param>

        /// <returns>A SqlDataReader</returns>

 

        // add version parameter

        public SqlDataReader GetMilestones(int ModuleId, WorkFlowVersion version)

        {

            // Create Instance of Connection and Command Object

            SqlConnection myConnection = PortalSettings.SqlConnectionString;

            SqlCommand myCommand = new SqlCommand("GetMilestones", myConnection);

 

            // Mark the Command as a SPROC

            myCommand.CommandType = CommandType.StoredProcedure;

 

            // Add Parameters to SPROC

            SqlParameter parameterModuleId = new SqlParameter("@ModuleId", SqlDbType.Int);

            parameterModuleId.Value = ModuleId;

            myCommand.Parameters.Add(parameterModuleId);

 

            // add version parameter to command

            SqlParameter parameterWorkflowVersion = new SqlParameter("@WorkflowVersion", SqlDbType.Int, 4);

            parameterWorkflowVersion.Value = (int)version;

            myCommand.Parameters.Add(parameterWorkflowVersion);

 

            // Execute the command

            myConnection.Open();

            SqlDataReader result = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

 

            // Return the datareader

            return result;

        }

 

 

        /// <summary>

        /// DeleteMilestones

        /// </summary>

        /// <param name="ItemId">ItemId</param>

        /// <returns>Void</returns>

        public void DeleteMilestones(int ItemId)

        {

            // Create Instance of Connection and Command Object

            SqlConnection myConnection = PortalSettings.SqlConnectionString;

            SqlCommand myCommand = new SqlCommand("DeleteMilestones", myConnection);

 

            // Mark the Command as a SPROC

            myCommand.CommandType = CommandType.StoredProcedure;

 

            // Add Parameters to SPROC

            SqlParameter parameterItemId = new SqlParameter("@ItemId", SqlDbType.Int);

            parameterItemId.Value = ItemId;

            myCommand.Parameters.Add(parameterItemId);

 

            // Execute the command

            myConnection.Open();

            myCommand.ExecuteNonQuery();

            myConnection.Close();

        }

 

 

        /// <summary>

        /// AddMilestones

        /// </summary>

        /// <param name="ItemId">ItemId</param>

        /// <returns>The newly created ID</returns>

        public int AddMilestones(int ItemId, int ModuleId, string CreatedByUser, DateTime CreatedDate, string Title, DateTime EstCompleteDate, string Status)

        {

            // Create Instance of Connection and Command Object

            SqlConnection myConnection = PortalSettings.SqlConnectionString;

            SqlCommand myCommand = new SqlCommand("AddMilestones", myConnection);

 

            // Mark the Command as a SPROC

            myCommand.CommandType = CommandType.StoredProcedure;

 

            // Add Parameters to SPROC

            SqlParameter parameterItemId = new SqlParameter("@ItemId", SqlDbType.Int);

            parameterItemId.Direction = ParameterDirection.Output;

            myCommand.Parameters.Add(parameterItemId);

 

            SqlParameter parameterModuleId = new SqlParameter("@ModuleId", SqlDbType.Int);

            parameterModuleId.Value = ModuleId;

            myCommand.Parameters.Add(parameterModuleId);

 

            SqlParameter parameterCreatedByUser = new SqlParameter("@CreatedByUser", SqlDbType.NVarChar, 100);

            parameterCreatedByUser.Value = CreatedByUser;

            myCommand.Parameters.Add(parameterCreatedByUser);

 

            SqlParameter parameterCreatedDate = new SqlParameter("@CreatedDate", SqlDbType.DateTime);

            parameterCreatedDate.Value = CreatedDate;

            myCommand.Parameters.Add(parameterCreatedDate);

 

            SqlParameter parameterTitle = new SqlParameter("@Title", SqlDbType.NVarChar, 100);

            parameterTitle.Value = Title;

            myCommand.Parameters.Add(parameterTitle);

 

            SqlParameter parameterEstCompleteDate = new SqlParameter("@EstCompleteDate", SqlDbType.DateTime);

            parameterEstCompleteDate.Value = EstCompleteDate;

            myCommand.Parameters.Add(parameterEstCompleteDate);

 

            SqlParameter parameterStatus = new SqlParameter("@Status", SqlDbType.NVarChar, 100);

            parameterStatus.Value = Status;

            myCommand.Parameters.Add(parameterStatus);

 

 

            // Execute the command

            myConnection.Open();

            SqlDataReader result = myCommand.ExecuteReader(CommandBehavior.CloseConnection);

 

            // Return the datareader

            return (int)parameterItemId.Value;

        }

 

 

        /// <summary>

        /// UpdateMilestones

        /// </summary>

        /// <param name="ItemId">ItemId</param>

        /// <returns>Void</returns>

        public void UpdateMilestones(int ItemId, int ModuleId, string CreatedByUser, DateTime CreatedDate, string Title, DateTime EstCompleteDate, string Status)

        {

            // Create Instance of Connection and Command Object

            SqlConnection myConnection = PortalSettings.SqlConnectionString;

            SqlCommand myCommand = new SqlCommand("UpdateMilestones", myConnection);

 

            // Mark the Command as a SPROC

            myCommand.CommandType = CommandType.StoredProcedure;

 

            // Update Parameters to SPROC

            SqlParameter parameterItemId = new SqlParameter("@ItemId", SqlDbType.Int);

            parameterItemId.Value = ItemId;

            myCommand.Parameters.Add(parameterItemId);

 

            SqlParameter parameterModuleId = new SqlParameter("@ModuleId", SqlDbType.Int);

            parameterModuleId.Value = ModuleId;

            myCommand.Parameters.Add(parameterModuleId);

 

            SqlParameter parameterCreatedByUser = new SqlParameter("@CreatedByUser", SqlDbType.NVarChar, 100);

            parameterCreatedByUser.Value = CreatedByUser;

            myCommand.Parameters.Add(parameterCreatedByUser);

 

            SqlParameter parameterCreatedDate = new SqlParameter("@CreatedDate", SqlDbType.DateTime);

            parameterCreatedDate.Value = CreatedDate;

            myCommand.Parameters.Add(parameterCreatedDate);

 

            SqlParameter parameterTitle = new SqlParameter("@Title", SqlDbType.NVarChar, 100);

            parameterTitle.Value = Title;

            myCommand.Parameters.Add(parameterTitle);

 

            SqlParameter parameterEstCompleteDate = new SqlParameter("@EstCompleteDate", SqlDbType.DateTime);

            parameterEstCompleteDate.Value = EstCompleteDate;

            myCommand.Parameters.Add(parameterEstCompleteDate);

 

            SqlParameter parameterStatus = new SqlParameter("@Status", SqlDbType.NVarChar, 100);

            parameterStatus.Value = Status;

            myCommand.Parameters.Add(parameterStatus);

 

 

            // Execute the command

            myConnection.Open();

            myCommand.ExecuteNonQuery();

            myConnection.Close();

        }

 

 

    }

}

Milestones.ascx.cs

Show the Milestones.ascx.cs code page and copy there the code below.

namespace Rainbow.DesktopModules

{

    using System;

    using System.Data;

    using System.Drawing;

    using System.Web;

    using System.Web.UI.WebControls;

    using System.Web.UI.HtmlControls;

 

    //Add Rainbow Namespaces

    using Rainbow.UI;

    using Rainbow.UI.WebControls;

    using Rainbow.Configuration;

 

    /// <summary>

    /// Summary description for Milestones.

    /// Notice we have changed base class from System.Web.UI.UserControl

    /// to Rainbow.UI.WebControls.PortalModuleControl

    /// </summary>

    /// Remove abstract, searchable classes cannot be abstract

    public class Milestones : Rainbow.UI.WebControls.PortalModuleControl

    {

        protected System.Web.UI.WebControls.DataGrid myDataGrid;

 

        private void Page_Load(object sender, System.EventArgs e)

        {

            // Create an instance of MilestonesDB class

            MilestonesDB milestones = new MilestonesDB();

 

            // Get the Milstones data for the current module.

            // ModuleId is defined on base class and contains

            // a reference to the current module.

            // and version is the requested version (production or staging)

            myDataGrid.DataSource = milestones.GetMilestones(ModuleId, Version);

 

            // Bind the milestones data to the grid.

            myDataGrid.DataBind();

        }

 

        /// <summary>

        /// Override base Guid implementation

        /// to provide an unique id for your control

        /// </summary>

        public override Guid GuidID

        {

            get

            {

                return new Guid("{B8784E32-688A-4b8a-87C4-DF108BF12DBE}");

            }

        }

 

        /// <summary>

        /// If the module is searchable you

        /// must override the property to return true

        /// </summary>

        public override bool Searchable

        {

            get

            {

                return true;

            }

        }

 

        public Milestones()

        {

            // indicates the support for workflow

            SupportsWorkflow = true;

        }

  

        /// <summary>

        /// Searchable module implementation

        /// </summary>

        /// <param name="portalID">The portal ID</param>

        /// <param name="userID">Id of the user is searching</param>

        /// <param name="searchString">The text to search</param>

        /// <param name="searchField">The fields where perfoming the search</param>

        /// <returns>The SELECT sql to perform a search on the current module</returns>

        public override string SearchSqlSelect(int portalID, int userID, string searchString, string searchField)

        {

            // Parameters:

            // Table Name: the table that holds the data

            // Title field: the field that contains the title for result, must be a field in the table

            // Abstract field: the field that contains the text for result, must be a field in the table

            // Search field: pass the searchField parater you recieve.

 

            Rainbow.Helpers.SearchDefinition s = new Rainbow.Helpers.SearchDefinition("Milestones", "Title", "Status", "CreatedByUser", "CreatedDate", searchField);

           

            //Add here extra search fields, this way

            //s.ArrSearchFields.Add("itm.ExtraFieldToSearch");

 

            // Builds and returns the SELECT query

            return s.SearchSqlSelect(portalID, userID, searchString);

        }

 

 

        #region Web Form Designer generated code

        override protected void OnInit(EventArgs e)

        {

            // CODEGEN: This call is required by the ASP.NET Web Form Designer.

            InitializeComponent();

 

            // Create a new Title the control

            ModuleTitle = new DesktopModuleTitle();

            // Set here title properties

            // Add support for the edit page

            ModuleTitle.AddUrl = "~/DesktopModules/MilestonesEdit.aspx";

            // Add title ad the very beginnig of

            // the control's controls collection

            Controls.AddAt(0, ModuleTitle);

 

            // Call base init procedure

            base.OnInit(e);

        }

       

        /// <summary>

        /// Required method for Designer support - do not modify

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()

        {

            this.Load += new System.EventHandler(this.Page_Load);

 

        }

        #endregion

    }

}

MilestonesEdit.aspx.cs

Show the MilestonesEdit.aspx.cs code page and copy there the code below.

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

using System.Data.SqlClient;

using Rainbow.Admin;

using Rainbow.Configuration;

 

namespace Rainbow.DesktopModules

{

    /// <summary>

    /// IBS Portal Milestone Module - Edit page part

    /// Writen by: Elaine Ossipov  - 9/11/2002 - admin@sbsc.net

    /// Moved into Rainbow by Jakob Hansen, hansen3000@hotmail.com

    /// Updated by Manu as Rainbow Tutorial

    /// </summary>

    public class MilestonesEdit : Rainbow.UI.EditItemPage

    {

        protected Rainbow.UI.WebControls.Globalized.Literal Literal1;

        protected Rainbow.UI.WebControls.Globalized.Literal Literal2;

        protected System.Web.UI.WebControls.TextBox TitleField;

        protected Rainbow.UI.WebControls.Globalized.RequiredFieldValidator Req1;

        protected Rainbow.UI.WebControls.Globalized.Literal Literal3;

        protected System.Web.UI.WebControls.TextBox EstCompleteDate;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req2;

        protected System.Web.UI.WebControls.CompareValidator VerifyCompleteDate;

        protected Rainbow.UI.WebControls.Globalized.Literal Literal4;

        protected System.Web.UI.WebControls.TextBox StatusBox;

        protected Rainbow.UI.WebControls.Globalized.RequiredFieldValidator Req3;

        protected System.Web.UI.WebControls.Label CreatedBy;

        protected System.Web.UI.WebControls.Label CreatedDate;

       

        private void Page_Load(object sender, System.EventArgs e)

        {

            // If the page is being requested the first time, determine if a

            // Milestone itemId value is specified, and if so,

            // populate the page contents with the Milestone details.

            if (Page.IsPostBack == false)

            {

                //Item id is defined in base class

                if (ItemId > 0)

                {

                    //Obtain a single row of Milestone information, the staging version!

                    MilestonesDB milestonesDB = new MilestonesDB();

                    SqlDataReader dr = milestonesDB.GetSingleMilestones(ItemId, WorkFlowVersion.Staging);

 

                    //Load the first row into the DataReader

                    dr.Read();

                    TitleField.Text = (String) dr["Title"];

                    EstCompleteDate.Text = ((DateTime) dr["EstCompleteDate"]).ToShortDateString();

                    StatusBox.Text = (String) dr["Status"];

                    CreatedBy.Text = (String) dr["CreatedByUser"];

                    CreatedDate.Text = ((DateTime) dr["CreatedDate"]).ToShortDateString();

                    dr.Close();

                }

                else

                {

                    //Provide defaults

                    EstCompleteDate.Text = DateTime.Now.AddDays(60).ToShortDateString();

                }

            }

        }

 

        /// <summary>

        /// This procedure is automaticall

        /// called on Update

        /// </summary>

        protected override void OnUpdate(EventArgs e)

        {

            // Calling base we check if the user has rights on updating

            base.OnUpdate(e);

 

            // Update onlyif the entered data is Valid

            if (Page.IsValid == true)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                if (ItemId <= 0)

                    milestonesDb.AddMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

                else

                    milestonesDb.UpdateMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

 

                // Redirects to the referring page

                // This method is provided by the base class

                this.RedirectBackToReferringPage();

            }

        }

 

        /// <summary>

        /// This procedure is automaticall

        /// called on Update

        /// </summary>

        override protected void OnDelete(EventArgs e)

        {

            // Calling base we check if the user has rights on deleting

            base.OnUpdate(e);

 

            if (ItemId > 0)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                milestonesDb.DeleteMilestones(ItemId);

            }

 

            // This method is provided by the base class

            this.RedirectBackToReferringPage();

        }

       

        #region Web Form Designer generated code

        /// <summary>

        /// Raises OnInitEvent

        /// </summary>

        /// <param name="e"></param>

        protected override void OnInit(EventArgs e)

        {

            InitializeComponent();

            base.OnInit(e);

        }

 

        /// <summary>

        /// Required method for Designer support - do not modify

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()

        {   

            this.Load += new System.EventHandler(this.Page_Load);

 

        }

        #endregion

   

    }

}

Using workflow
 

1)     Go on Module properties and enable workflow support for specific module (You cannot enable support is the module does not support workflow).

2)     Then define roles that can Approve and Publish the module contents.

3)     Modify the staging version.

4)     When you are ready submit it for approbation. You can send an email to authorized roles so that the can know that ere is something to publish.

5)     The authorized roles can then approve or reject  the new content.
When they reject it, the module comes back available for editing by the editors. An email can be sent to the editors to explain what the problem is.
When they approve it, an email can be sent to the publisher.

6)     Publishers can then publish the new content.

Add Support for an Edit Page

In order to allow users to edit and add additional Milestones, support for an edit page must be added.

This support is added by defining EditUrl to the Module Title Control, if you have a page that edits the content of your module directly, like HTML module; or defining AddUrl, if you have a page for adding items to the control (like this Milestones Module).

An optional EditText or AddText can be defined. This text is a localization Key and will be localized.

The naming convention starts with ModuleName and then adds the function.

e.g. MilestonesEdit

Create the Edit Page

Once we have added support for an edit page using the Title User Control, we need to create the actual edit page. In addition to the HTML, we will define four methods:

·         Page_Load

·         OnUpdate

·         OnDelete

·         OnCancel (already defined in base class)

 

using System;

using System.Collections;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.HtmlControls;

using System.Data.SqlClient;

using Rainbow.Admin;

 

namespace Rainbow.DesktopModules

{

    /// <summary>

    /// IBS Portal Milestone Module - Edit page part

    /// Writen by: Elaine Ossipov  - 9/11/2002 - admin@sbsc.net

    /// Moved into Rainbow by Jakob Hansen, hansen3000@hotmail.com

    /// Updated by Manu as Rainbow Tutorial

    /// </summary>

    public class MilestonesEdit : Rainbow.UI.EditItemPage

    {

        protected System.Web.UI.WebControls.TextBox TitleField;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req1;

        protected System.Web.UI.WebControls.TextBox EstCompleteDate;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req2;

        protected System.Web.UI.WebControls.CompareValidator VerifyCompleteDate;

        protected System.Web.UI.WebControls.TextBox StatusBox;

        protected System.Web.UI.WebControls.RequiredFieldValidator Req3;

        protected System.Web.UI.WebControls.Label CreatedBy;

        protected System.Web.UI.WebControls.Label CreatedDate;

       

        private void Page_Load(object sender, System.EventArgs e)

        {

            // If the page is being requested the first time, determine if a

            // Milestone itemId value is specified, and if so,

            // populate the page contents with the Milestone details.

            if (Page.IsPostBack == false)

            {

                //Item id is defined in base class

                if (ItemId > 0)

                {

                    //Obtain a single row of Milestone information.

                    MilestonesDB milestonesDB = new MilestonesDB();

                    SqlDataReader dr = milestonesDB.GetSingleMilestones(ItemId);

 

                    //Load the first row into the DataReader

                    dr.Read();

                    TitleField.Text = (String) dr["Title"];

                    EstCompleteDate.Text = ((DateTime) dr["EstCompleteDate"]).ToShortDateString();

                    StatusBox.Text = (String) dr["Status"];

                    CreatedBy.Text = (String) dr["CreatedByUser"];

                    CreatedDate.Text = ((DateTime) dr["CreatedDate"]).ToShortDateString();

                    dr.Close();

                }

            }

        }

 

        /// <summary>

        /// This procedure is automatically

        /// called on Update

        /// </summary>

        override protected void OnUpdate()

        {

            // Calling base we check if the user has rights on updating

            base.OnUpdate();

 

            // Update only if the entered data is Valid

            if (Page.IsValid == true)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                if (ItemId <= 0)

                    milestonesDb.AddMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

                else

                    milestonesDb.UpdateMilestones(ItemId, ModuleId, Context.User.Identity.Name, DateTime.Now, TitleField.Text, DateTime.Parse(EstCompleteDate.Text), StatusBox.Text);

 

                // Redirects to the referring page

                // This method is provided by the base class

                this.RedirectBackToReferringPage();

            }

        }

 

        /// <summary>

        /// This procedure is automatically

        /// called on Update

        /// </summary>

        override protected void OnDelete()

        {

            // Calling base we check if the user has rights on updating

            base.OnUpdate();

 

            if (ItemId > 0)

            {

                MilestonesDB milestonesDb = new MilestonesDB();

                milestonesDb.DeleteMilestones(ItemId);

            }

 

            // This method is provided by the base class

            this.RedirectBackToReferringPage();

        }

       

        #region Web Form Designer generated code

        /// <summary>

        /// Raises OnInitEvent

        /// </summary>

        /// <param name="e"></param>

        protected override void OnInit(EventArgs e)

        {

            InitializeComponent();

            base.OnInit(e);

        }

 

        /// <summary>

        /// Required method for Designer support - do not modify

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()

        {    

            this.Load += new System.EventHandler(this.Page_Load);

        }

        #endregion

   

    }

}

Adding the New Portal Module to the Framework

The Milestones Portal Module is now complete.  We have to rebuild our project before adding it to the portal.

The only step left to perform is to add the module to the portal framework by using the online administrator’s Module Definitions section. You can find it on the Admin All page because the Modules are installed to be common to all portals.

In this section, click on the Add New Module Type to bring up the page shown.  Enter the information for the new module and click Update. Remember to select the portals you want to be able to use the module. The module can then be added to the different tabs by using the online administrator’s “Tab Name and Layout” section.

If you get an error like: “Invalid module! External component has thrown an exception” there is an error parsing the control (the ascx html part). To get more information about the specific error create an empty webform and add the control. Running the form may give a clue to identify and resolve the problem.

Figure 2. Module Type Definition

Conclusion

The Rainbow Portal demonstrates the key techniques used to build a portal web application using ASP.NET. In addition to web-based administration and content management, the portal is also extremely easy to extend as shown in this white paper with the Milestone module. This sample provides a great reference in terms of learning the .NET technologies as well as a powerful framework that can be used for Internet or intranet portals.

For More Information

·         The complete documentation and source code can be obtained at http://www.rainbowportal.net

·         Forums at: http://www.rainbowportal.net/AspNetForums

posted on 2005-12-14 16:52  Mint  阅读(565)  评论(0)    收藏  举报