Portal Starter Kit: Design and Implementation
Vertigo Software, Inc.
November 2002
Updated to ASP.net 2.0 by Mark Kendall
Source Code
Requirements:
1. ASP 3.5 Framework
2. SQL Server
3. Visual Studio 2008
4. IIS
· No Assemblies in this project- all BLL code has been moved to the APP_Code directory
· Desktop Modules converted to have Partial Classes
· AJAX Enabled
· Script Manager On Default.aspx
· Milestone User control Reworked
· VB Only
· New Module Advanced Data Grid for illustrative purposes
· Good start to Intranet
· Tested
· Windows Authentication Tested
Summary: This article describes the design and architecture decisions for the ASP.NET Portal Starter Kit. In addition, a detailed look and explanation of the code required to extend it is also covered.
This article brings the Portal Starter Kit up to date as far as VB.net is concerned. The aim of this upgrade is provide a framework for .net developers who might be charged with building an intranet and need a good stating point. As we all know, this starter kit was the basis for Dotnetnuke, and we think it still has potential in the intranet area of many companies.
Overview
What is the Portal Starter Kit?
The Portal Starter Kit 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 portal demonstrates many features offered by the ASP.NET technology including:
· Cross-browser support for Netscape and Internet Explorer
· Mobile device support for WAP/WML and Pocket Browser devices
· Clean code/html content separation using server controls
· Pages that are constructed from dynamically-loaded user controls
· Configurable output caching of portal page regions
· 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 portal in depth and provides insight from the perspective of the creators. In addition, the article covers how the 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 Portal Starter Kit was developed by Microsoft and updated to the latest version of ASP.net by Mark Kendall. The database was moved to SQL Server because I like the performance and reliability of a solid database such as SQL Server.
Application Architecture
The portal uses a multi-tier application architecture. The Portal contains two data sources. The configuration settings are stored in PortalCFG.xml and the content for the application is stored in a SQL Server database. The data access is provided through classes that reside in the app_code directory 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 content for the portal is 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 portal.
Database Schema
The portal database schema is a very simple schema that contains a simple repository for Module content and 3 tables to store Users and User Roles. The physical schema is shown in Figure 1.
Figure 1. Physical Database Schema
Portal Configuration XML Schema
The schema based on the PortalCFG.xml file contains all the configuration settings for the Portal. The schema is simple and easy to understand. The XML Configuration file stores all the high level Portal, Tab and Module Definitions. The configuration settings are stored in a cache and GetSiteSettings() only reads from the xml file if the settings have changed. The physical schema is shown in Figure 2.
Figure 2. PortalCFG.xsd Schema
Stored Procedures
The 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 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 what the portal framework consists of as well as how it was built.
Portal Settings
The portal settings are represented by the PortalSettings Class, which is defined in the Configuration business component. These settings include the following:
· The Portal ID
· The Portal Name
· The Desktop Tabs Collection
· The Mobile Tabs Collection
· The Currently Active Tab
· The “AlwaysShowEditButton” Setting
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 file.
Context.Items.Add("PortalSettings", New PortalSettings(tabIndex, tabId))
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”.
Dim portalSettings As portalSettings = _
CType(HttpContext.Current.Items("PortalSettings"), portalSettings)
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.
Dim portalSettings As portalSettings = _
CType(HttpContext.Current.Items("PortalSettings"), portalSettings)
Dim i As Integer
For i = 0 To portalSettings.DesktopTabs.Count - 1
Dim tab As TabStripDetails = CType(portalSettings.DesktopTabs(i), TabStripDetails)
Next I
The display of the tabs (as shown in Figure 2 below) is handled in the DesktopPortalBanner.ascx user control. The control iterates through the tabs collection, in the same way shown above, 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.
Figure 2. Portal Tabs
When a user clicks on a given tab, the PortalSettings object is updated to include 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 the Portal Starter Kit. 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 portal comes with eleven built-in portal modules that are available “out of the box,” seven of which are shown in Figure 3. Portal Modules.
Figure 3. Portal Modules
Portal Security
The security design in the portal makes use of both authentication and authorization. Authentication is the process in which the application verifies a user’s identity and credentials. Authorization will actually verify the authenticated user’s permissions for a 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 the 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. 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 to access the administration functionality.
For example in the Page_Load event in the admin Tabs.ascx user control, a call is made to IsInRoles():
' Verify that the current user has access to access this page
If PortalSecurity.IsInRoles("Admins") = False Then
Response.Redirect("~/Admin/EditAccessDenied.aspx")
End If
The current users’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 can be used to verify whether the current user is in a specific role.
Sub Application_AuthenticateRequest(ByVal sender As Object, _
ByVal e As EventArgs)
If Request.IsAuthenticated = True Then
Dim roles() As String
' Create the roles cookie if it doesn't exist yet for
' this session.
If Request.Cookies("portalroles") Is Nothing Then
' Get roles from UserRoles table, and add to cookie
Dim _user As New UsersDB()
roles = _user.GetRoles(User.Identity.Name)
' Create a string to persist the roles
Dim roleStr As String = ""
Dim role As String
For Each role In roles
roleStr += role
roleStr += ";"
Next role
' Create a cookie authentication ticket.
' version
' user name
' issue time
' expires every hour
' don't persist cookie
' roles
Dim ticket As New FormsAuthenticationTicket(1, _
Context.User.Identity.Name, _
DateTime.Now, _
DateTime.Now.AddHours(1), _
False, _
roleStr)
' Encrypt the ticket
Dim cookieStr As String = FormsAuthentication.Encrypt(ticket)
' Send the cookie to the client
Response.Cookies("portalroles").Value = cookieStr
Response.Cookies("portalroles").Path = "/"
Response.Cookies("portalroles").Expires = _
DateTime.Now.AddMinutes(1)
Else
' Get roles from roles cookie
Dim ticket As FormsAuthenticationTicket = _
FormsAuthentication.Decrypt(Context.Request.Cookies("portalroles").Value)
'convert the string representation of the role data
'into a string array
Dim userRoles As New ArrayList()
Dim role As String
For Each role In ticket.UserData.Split(New Char() {";"c})
userRoles.Add(role)
Next role
roles = CType(userRoles.ToArray(GetType(String)), String())
End If
' Add our own custom principal to the request containing
' the roles in the auth ticket
Context.User = New GenericPrincipal(Context.User.Identity, roles)
End If
End Sub
The database calls for all of the role-based checks are contained in Security.vb.
Administering the ASP.NET Portal Starter Kit
The 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 that takes them to the administration tool. This tool is shown in Figure 4 below.
Figure 4. Portal Administration
The portal administration allows the user to perform a variety of site management and configuration tasks. This is place where new modules can be added, tabs that appear horizontal across the top of the site can be configured, and security roles are defined.
Extending the ASP.NET Portal Starter Kit
The 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 5.
Figure 5. Milestones Table Above
The Stored Procedures:
USE [Portal]GO/****** Object: Table [dbo].[Milestones] Script Date: 07/31/2008 15:37:46 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGOCREATE TABLE [dbo].[Milestones]( [ItemID] [int] IDENTITY(1,1) NOT NULL, [ModuleID] [int] NULL, [CreatedByUser] [nchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [CreatedDate] [datetime] NULL CONSTRAINT [DF_Milestones_CreatedDate] DEFAULT (getdate()), [Title] [nchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, [EstCompleteDate] [datetime] NULL, [Status] [nchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, CONSTRAINT [PK_Milestones] PRIMARY KEY CLUSTERED ( [ItemID] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] USE [Portal]GO/****** Object: StoredProcedure [dbo].[Portal_AddMilestone] Script Date: 07/31/2008 15:50:25 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO CREATE PROCEDURE [dbo].[Portal_AddMilestone]( @ModuleID int, @Title nvarchar(100), @UserName nvarchar(250), @Status nvarchar(250), @CompleteddateTime datetime, @ItemID int OUTPUT)AS INSERT INTO Milestones( ModuleID, CreatedByUser, CreatedDate, Title, EstCompleteDate, Status )VALUES( @ModuleID, @UserName, GetDate(), @Title, @CompleteddateTime , @Status ) SELECT @ItemID = @@Identity USE [Portal]GO/****** Object: StoredProcedure [dbo].[Portal_DeleteMilestone] Script Date: 07/31/2008 15:50:48 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO CREATE PROCEDURE [dbo].[Portal_DeleteMilestone]( @ItemID int)AS DELETE FROM Milestones WHERE ItemID = @ItemID USE [Portal]GO/****** Object: StoredProcedure [dbo].[Portal_GetMilestones] Script Date: 07/31/2008 15:51:17 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO CREATE PROCEDURE [dbo].[Portal_GetMilestones]( @ModuleID int)AS SELECT * from Milestones WHERE ModuleID = @ModuleID USE [Portal]GO/****** Object: StoredProcedure [dbo].[Portal_GetSingleMilestone] Script Date: 07/31/2008 15:51:50 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO CREATE PROCEDURE [dbo].[Portal_GetSingleMilestone]( @ItemID int)AS SELECT * FROM Milestones WHERE ItemID = @ItemID USE [Portal]GO/****** Object: StoredProcedure [dbo].[Portal_UpdateMilestone] Script Date: 07/31/2008 15:52:19 ******/SET ANSI_NULLS ONGOSET QUOTED_IDENTIFIER ONGO CREATE PROCEDURE [dbo].[Portal_UpdateMilestone]( @ModuleID int, @item int, @Title nvarchar(100), @UserName nvarchar(250), @Status nvarchar(250), @CompleteddateTime datetime, @ItemID int OUTPUT)AS update Milestones set ModuleID=@ModuleID ,CreatedByUser= @UserName,EstCompleteDate=@CompleteddateTime, Title=@Title , Status=@Status where itemid=@item SELECT @ItemID = 1
Next, we will create the stored procedures required to handle access to the Milestones table. The stored procedures we need are:
· AddMilestone
· DeleteMilestone
· GetMilestone
· GetSingleMilestone
· UpdateMilestone
After the stored procedures are created we need to create a data access layer (DAL) component to provide access to the Milestone procedures. We will define the following methods, the first of which is shown in Listing 1 below. ASPNET.StarterKit.Portal.MilestonesDB.GetMilestones()
· ASPNET.StarterKit.Portal.MilestonesDB.GetSingleMilestone()
· ASPNET.StarterKit.Portal.MilestonesDB.DeleteMilestone()
· ASPNET.StarterKit.Portal.MilestonesDB.AddMilestone()
· ASPNET.StarterKit.Portal.MilestonesDB.UpdateMilestone()
Imports Microsoft.VisualBasic
Imports System
Imports System.Configuration
Imports System.Data
Imports System.Data.SqlClient
Imports MK.Portal
Namespace MK.Portal
Public Class MilestonesDB
Public Function GetMilestones(ByVal moduleId As Integer) As DataSet
' Create Instance of Connection and Command Object
Dim myConnection As _
New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
Dim myCommand As New SqlDataAdapter("Portal_GetMilestones", myConnection)
' Mark the Command as a SPROC
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure
' Add Parameters to SPROC
Dim parameterModuleId As New SqlParameter("@ModuleId", SqlDbType.Int, 4)
parameterModuleId.Value = moduleId
myCommand.SelectCommand.Parameters.Add(parameterModuleId)
' Create and Fill the DataSet
Dim myDataSet As New DataSet()
myCommand.Fill(myDataSet)
' Return the DataSet
Return myDataSet
End Function
Public Function AddMilestones(ByVal moduleId As Integer, ByVal user As String, ByVal item As String, ByVal Title As String, ByVal CompleteddateTime As String, ByVal status As String) As Integer
If user.Length < 1 Then
user = "unknown"
End If
' Create Instance of Connection and Command Object
Dim myConnection As New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
Dim myCommand As New SqlCommand("Portal_AddMilestone", myConnection)
' Mark the Command as a SPROC
myCommand.CommandType = CommandType.StoredProcedure
' Add Parameters to SPROC
Dim parameterItemID As New SqlParameter("@ItemID", SqlDbType.Int, 4)
parameterItemID.Direction = ParameterDirection.Output
myCommand.Parameters.Add(parameterItemID)
Dim parameterModuleID As New SqlParameter("@ModuleID", SqlDbType.Int, 4)
parameterModuleID.Value = moduleId
myCommand.Parameters.Add(parameterModuleID)
Dim parameterUserName As New SqlParameter("@UserName", SqlDbType.NVarChar, 100)
parameterUserName.Value = user
myCommand.Parameters.Add(parameterUserName)
Dim parameterTitle As New SqlParameter("@Title", SqlDbType.NVarChar, 100)
parameterTitle.Value = Title
myCommand.Parameters.Add(parameterTitle)
Dim parameterDescription As New SqlParameter("@CompleteddateTime", SqlDbType.DateTime)
parameterDescription.Value = CompleteddateTime
myCommand.Parameters.Add(parameterDescription)
Dim parameterUrl As New SqlParameter("@Status", SqlDbType.NVarChar, 100)
parameterUrl.Value = status
myCommand.Parameters.Add(parameterUrl)
myConnection.Open()
myCommand.ExecuteNonQuery()
myConnection.Close()
Return CInt(parameterItemID.Value)
End Function
Public Function UpdateMilestones(ByVal moduleId As Integer, ByVal item As String, ByVal user As String, ByVal Title As String, ByVal completeddateTime As String, ByVal status As String) As Integer
If user.Length < 1 Then
user = "unknown"
End If
' Create Instance of Connection and Command Object
Dim myConnection As New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
Dim myCommand As New SqlCommand("Portal_UpdateMilestone", myConnection)
' Mark the Command as a SPROC
myCommand.CommandType = CommandType.StoredProcedure
' Add Parameters to SPROC
Dim parameterItemID As New SqlParameter("@ItemID", SqlDbType.Int, 4)
parameterItemID.Direction = ParameterDirection.Output
myCommand.Parameters.Add(parameterItemID)
Dim parameterModuleID As New SqlParameter("@ModuleID", SqlDbType.Int, 4)
parameterModuleID.Value = moduleId
myCommand.Parameters.Add(parameterModuleID)
Dim parameteritem As New SqlParameter("@item", SqlDbType.Int, 4)
parameteritem.Value = item
myCommand.Parameters.Add(parameteritem)
Dim parameterUserName As New SqlParameter("@UserName", SqlDbType.NVarChar, 100)
parameterUserName.Value = user
myCommand.Parameters.Add(parameterUserName)
Dim parameterTitle As New SqlParameter("@Title", SqlDbType.NVarChar, 100)
parameterTitle.Value = Title
myCommand.Parameters.Add(parameterTitle)
Dim parameterDescription As New SqlParameter("@CompleteddateTime", SqlDbType.DateTime)
parameterDescription.Value = CompleteddateTime
myCommand.Parameters.Add(parameterDescription)
Dim parameterUrl As New SqlParameter("@Status", SqlDbType.NVarChar, 100)
parameterUrl.Value = status
myCommand.Parameters.Add(parameterUrl)
myConnection.Open()
myCommand.ExecuteNonQuery()
myConnection.Close()
Return CInt(parameterItemID.Value)
End Function
Public Function GetSingleMilestone(ByVal itemId As Integer) As System.Data.SqlClient.SqlDataReader
' Create Instance of Connection and Command Object
Dim myConnection As New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
Dim myCommand As New SqlCommand("Portal_GetSingleMilestone", myConnection)
' Mark the Command as a SPROC
myCommand.CommandType = CommandType.StoredProcedure
' Add Parameters to SPROC
Dim parameterItemId As New SqlParameter("@ItemID", SqlDbType.Int, 4)
parameterItemId.Value = itemId
myCommand.Parameters.Add(parameterItemId)
' Execute the command
myConnection.Open()
Dim result As System.Data.SqlClient.SqlDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection)
' Return the datareader
Return result
End Function
Public Sub DeleteMileStone(ByVal itemID As Integer)
' Create Instance of Connection and Command Object
Dim myConnection As New SqlConnection(ConfigurationSettings.AppSettings("connectionString"))
Dim myCommand As New SqlCommand("Portal_DeleteMilestone", myConnection)
' Mark the Command as a SPROC
myCommand.CommandType = CommandType.StoredProcedure
' Add Parameters to SPROC
Dim parameterItemID As New SqlParameter("@ItemID", SqlDbType.Int, 4)
parameterItemID.Value = itemID
myCommand.Parameters.Add(parameterItemID)
myConnection.Open()
myCommand.ExecuteNonQuery()
myConnection.Close()
End Sub
End Class
End Namespace
' Other methods elided for clarity.
End Class
End Namespace
Listing 1
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. The implementation of the user control is shown in Listing 2 below.
<%@ Control Language="VB" AutoEventWireup="false" CodeFile="MileStones.ascx.vb" Inherits="MK.Portal.DesktopModules_MileStones" %>
<%@ Register TagPrefix="ASPNETPortal" TagName="Title" Src="~/DesktopModuleTitle.ascx"%>
<aspnetportal:title editurl="~/DesktopModules/EditMilestones.aspx" edittext="Add Milestone" runat="server" id="Title1" />
<asp:DataGrid
id="myDataGrid"
HeaderStyle-CssClass="Normal"
HeaderStyle-Font-Bold="true"
ItemStyle-CssClass="Normal"
AutoGenerateColumns="false"
CellPadding="5"
Border="0"
Width="100%"
EnableViewState="false"
runat="server">
<Columns>
<asp:TemplateColumn>
<ItemTemplate>
<asp:HyperLink
id="editLink"
ImageUrl="~/images/edit.gif"
NavigateUrl='<%# "~/DesktopModules/EditMilestones.aspx?ItemID=" & DataBinder.Eval(Container.DataItem,"ItemID") & "&mid=" & ModuleId %>' Visible="<%# IsEditable %>" runat="server" />
</ItemTemplate>
</asp:TemplateColumn>
<asp:BoundColumn DataField="Title" HeaderText="Title"
runat="server" />
<asp:BoundColumn DataField="EstCompleteDate"
HeaderText="Comp. Date" runat="server"
DataFormatString="{0:d}" />
<asp:BoundColumn DataField="Status" HeaderText="Status" runat="server" />
</Columns>
</asp:DataGrid>
And the code behind:
Namespace MK.Portal
Partial Class DesktopModules_MileStones
Inherits MK.Portal.PortalModuleControl
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim milestones As New MK.Portal.MilestonesDB()
myDataGrid.DataSource = milestones.GetMilestones(ModuleId)
myDataGrid.DataBind()
End Sub
End Class
End Namespace
Listing 2
Inherit the ASPNET.StarterKit.Portal 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. To inherit the base class, place the following line at the top of the user control.
<%@ Control language="VB" Inherits=" ASPNET.StarterKit.Portal
.PortalModuleControl" %>
Add the Module Title
One of the user controls that are available to portal modules is the Title user control. This control will generate the appropriate HTML.
<portal:title runat="server" />
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. Support is added by passing two additional properties to the Module Title User Control.
@ Page Language="VB" AutoEventWireup="false" CodeFile="EditMilestones.aspx.vb" Inherits="MK.Portal.DesktopModules_EditMilestones" %>
<%@ Register TagPrefix="ASPNETPortal" TagName="Banner" Src="~/DesktopPortalBanner.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<link rel="stylesheet" href='<%= MK.Portal.Global.GetApplicationPath(Request) & "/ASPNETPortal.css" %>' type="text/css">
</head>
<body leftmargin="0" bottommargin="0" rightmargin="0" topmargin="0" marginheight="0" marginwidth="0">
<form id="Form1" runat="server">
<table width="100%" cellspacing="0" cellpadding="0" border="0">
<tr valign="top">
<td colspan="2">
<aspnetportal:banner id="SiteHeader" runat="server" />
</td>
</tr>
<tr>
<td>
<br>
<table width="98%" cellspacing="0" cellpadding="4" border="0">
<tr valign="top">
<td width="150">
</td>
<td width="*">
<table width="500" cellspacing="0" cellpadding="0">
<tr>
<td align="left" class="Head">
Milestone Details
</td>
</tr>
<tr>
<td colspan="2">
<hr noshade size="1">
</td>
</tr>
</table>
<table width="750" cellspacing="0" cellpadding="0" border="0">
<tr>
<td width="100" class="SubHead">
Title:
</td>
<td rowspan="3">
</td>
<td>
<asp:textbox id="TitleField" cssclass="NormalTextBox" width="390" columns="30" maxlength="150" runat="server" />
</td>
<td width="25" rowspan="3">
</td>
<td class="Normal" width="250">
<asp:requiredfieldvalidator id="Req1" display="Static" errormessage="You Must Enter a Valid Title" controltovalidate="TitleField" runat="server" />
</td>
</tr>
<tr>
<td class="SubHead">
Completed:
</td>
<td>
<asp:textbox id="CompleteDateField" cssclass="NormalTextBox" width="390"
columns="30" maxlength="150" runat="server" />
</td>
<td class="Normal">
<asp:requiredfieldvalidator id="Req2" display="Static" runat="server"
errormessage="You Must Enter a Valid Date"
controltovalidate="CompleteDateField" />
</td>
</tr>
<tr>
<td class="SubHead">
Status:
</td>
<td>
<asp:textbox id="StatusField" cssclass="NormalTextBox" width="390" columns="30"
maxlength="150" runat="server" />
</td>
<td class="Normal">
<asp:requiredfieldvalidator id="Requiredfieldvalidator1" display="Static" runat="server"
errormessage="You Must Enter a Valid Status"
controltovalidate="StatusField" />
</td>
</tr>
</table>
<asp:linkbutton id="updateButton" text="Update" runat="server" cssclass="CommandButton" borderstyle="none" />
<asp:linkbutton id="cancelButton" text="Cancel" causesvalidation="False" runat="server" cssclass="CommandButton" borderstyle="none" />
<asp:linkbutton id="deleteButton" text="Delete this item" causesvalidation="False" runat="server" cssclass="CommandButton" borderstyle="none" />
<hr noshade size="1" width="500">
<span class="Normal">Created by
<asp:label id="CreatedBy" runat="server" />
on
<asp:label id="CreatedDate" runat="server" />
<br>
</span>
<p>
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>
And the code behind:
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
· UpdateBtn_Click
· DeleteBtn_Click
· CancelBtn_Click
The source for the DeleteBtn_Click method and the HTML associated with it in the edit page is shown in Listing 3 below. The rest of the implementation may be found in EditMilestones.aspx as part of the Milestone Extension download.
Imports System.Data.SqlClient
Namespace MK.Portal
Partial Class DesktopModules_EditMilestones
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
' Determine ModuleId of Links Portal Module
moduleId = Int32.Parse(Request.Params("Mid"))
' Verify that the current user has access to edit this module
If PortalSecurity.HasEditPermissions(moduleId) = False Then
Response.Redirect("~/Admin/EditAccessDenied.aspx")
End If
' Determine ItemId of Link to Update
If Not (Request.Params("ItemId") Is Nothing) Then
itemId = Int32.Parse(Request.Params("ItemId"))
End If
If Page.IsPostBack = False Then
' Store URL Referrer to return to portal
ViewState("UrlReferrer") = Request.UrlReferrer.ToString()
If itemId <> 0 Then
' Obtain a single row of link information
Dim milestones As New MilestonesDB()
Dim dr As System.Data.SqlClient.SqlDataReader = milestones.GetSingleMilestone(itemId)
' Read in first row from database
dr.Read()
' Security check. verify that itemid is within the module.
Dim dbModuleID As Integer = Convert.ToInt32(dr("ModuleID"))
If dbModuleID <> moduleId Then
dr.Close()
Response.Redirect("~/Admin/EditAccessDenied.aspx")
End If
TitleField.Text = CStr(dr("Title"))
CompleteDateField.Text = CStr(dr("EstCompleteDate"))
Me.StatusField.Text = CStr(dr("Status"))
CreatedBy.Text = CStr(dr("CreatedByUser"))
CreatedDate.Text = CType(dr("CreatedDate"), DateTime).ToShortDateString()
' Close datareader
dr.Close()
End If
End If
End Sub
Private itemId As Integer = 0
Private moduleId As Integer = 0
Private Sub CancelBtn_Click(ByVal sender As Object, ByVal e As EventArgs) Handles cancelButton.Click
' Redirect back to the portal home page
Response.Redirect(CStr(ViewState("UrlReferrer")))
End Sub
Protected Sub updateButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles updateButton.Click
If Page.IsValid = True Then
' Create an instance of the Link DB component
Dim milestones As New MK.Portal.MilestonesDB()
If itemId = 0 Then
' Add the link within the Links table
milestones.AddMilestones(moduleId, itemId, Context.User.Identity.Name, TitleField.Text, Me.CompleteDateField.Text, Me.StatusField.Text)
Else
' Update the link within the Links table
milestones.UpdateMilestones(moduleId, itemId, Context.User.Identity.Name, TitleField.Text, Me.CompleteDateField.Text, Me.StatusField.Text)
End If
' Redirect back to the portal home page
Response.Redirect(CStr(ViewState("UrlReferrer")))
End If
End Sub
Protected Sub deleteButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles deleteButton.Click
If itemId <> 0 Then
Dim milestone As New MK.Portal.MilestonesDB()
milestone.DeleteMilestone(itemId)
End If
Response.Redirect(CType(ViewState("UrlReferrer"), String))
End Sub
End Class
End Namespace
Listing 3
Adding the New Portal Module to the Framework
The Milestones Portal Module is now complete. The only step left to perform is to add the module to the portal framework by using the online administrator’s Module Definitions section. In this section, click on the Add New Module Type to bring up the page shown in Figure 6. Enter the information for the new module and click Update. The module can then be added to the different tabs by using the online administrator’s “Tab Name and Layout” section.
Figure 6. Module Type Definition
Conclusion
The 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 powerful framework that can be used for Internet or intranet portals.
For More Information
· The complete documentation and source code can be obtained at Kendallsoft