Introduction
This article describes how I build my Personal Portal (in other words: a
Homepage). The idea was to develop a Portal that is easy to deploy (e.g. no
Database) and easy to use.
These technical problems are solved in this solution
-
Storage The portal doesn't use a database. Instead XML-Files are used.
Reading/Writing is done with the XML Serializer and
Dataset
s.
-
Dynamically loading of Web User Controls
The portal has Tabs and Modules. Modules are Web User Controls which are loaded
at runtime. The definition is stored in a XML File.
-
Authentication
Forms Authentication is used. Users and Roles are stored in a XML File.
-
HttpHandler A HttpHandler is used to access Tab-Pages.
Background
First I wanted to use the
IBuySpy Portal. But it isn't (or wasn't) free and uses a database.
After some searching at Codeproject and Sourceforge
I decided to implement my own Portal.
This Project is hosted at Sourceforge.
I would be happy about feedback and suggestions
. Also new modules or improvements are welcome. A Demo Portal can be found
here .
Using the code
Feel free to use the Portal under the GPL License or code snippets freely.
Summary
A Portal has Tabs. Each Tab can have Sub-Tabs and Modules. Modules are providing
content. A Module can be placed right, in the middle or on the left side on a
Tab.
A Module consists of a View Web User Control and a Edit Web User Control. The
edit control is optional. Both controls are derived from the Module/EditModule
class. Those classes are providing information (Reference, Name, etc.) and a
configuration infrastructure. Modules are located in the Modules/<ModuleName>
directory.
Tabs and Modules have Roles assigned. Users in those roles may view/edit
Tabs/Modules. There are four built-in roles:
-
Admin
- Administrator Role. May edit/view everything
-
User
- Signed in User
-
Everyone
- Signed in User or Anonymous User
-
Anonymous - Not signed in User
The Portal definition and Users are stored in a XML File.
Currently there is a "Frame" and a "Table" version (configured in the web.config
file). This is how the Portal is rendered: In Frames or in a Table.
Classes
Class Name |
Description |
Definitions |
PortalDefinition |
This is the main Portal Definition class. It contains various helper functions
and a list of Tabs. |
PortalDefinition.Tab |
The Tab Definition. A Tab has a Name, Reference, Roles, Sub Tabs and
left/middle/right Modules. |
PortalDefinition.Module |
A Module has a Name, Reference and Roles. |
PortalDefinition.Role |
A Role is just a Name and Base Class for View or Edit Roles |
PortalDefinition.EditRole |
Edit Role. |
PortalDefinition.ViewRole |
View Role. |
Users |
User/Roles Dataset. |
ModuleSettings |
Defines Module Settings like the Module Control Name. |
Controls/Rendering |
EditLink |
Renders the Edit Link. |
ModuleFailed |
Renders a "Module failed to load". |
ModuleHeader |
Renders the Module Header. |
OverlayMenu |
Renders a Menu. |
OverlayMenuItem |
Renders a Menu Item. |
PortalTab |
This is the main Protal Render Control! |
TabMenu |
Renders the Tab Menu. |
TabPath |
Renders the current Tab Path. |
TabHttpHandler |
The HttpModule for "translating" URLs. |
Helper |
Helper |
Common Helper Class. |
UserManagement |
Usermanagement Methods. |
API |
Config |
Configuration Helpers. |
EditModule |
Base Class for Module Edit Controls. |
Module |
Base Class for Module View Controls. |
ASPX-Pages are used as container for Web User Controls. They have hardly program
logic.
Storage
Two things must be stored: the Portal Definition and Users/Roles.
The Portal definition is stored with the XML-Serializer.
[XmlRoot("portal"), Serializable]
public class PortalDefinition
{
private static XmlSerializer xmlPortalDef =
new XmlSerializer(typeof(PortalDefinition));
...
public static PortalDefinition Load()
{
XmlTextReader xmlReader = new XmlTextReader(
Config.GetPortalDefinitionPhysicalPath());
PortalDefinition pd = (PortalDefinition)xmlPortalDef.Deserialize(
xmlReader);
xmlReader.Close();
return pd;
}
public void Save()
{
XmlTextWriter xmlWriter = new XmlTextWriter(
Config.GetPortalDefinitionPhysicalPath(), System.Text.Encoding.UTF8);
xmlWriter.Formatting = Formatting.Indented;
xmlPortalDef.Serialize(xmlWriter, this);
xmlWriter.Close();
}
...
}
Users are stored in a Dataset
public class UserManagement
{
...
public static Users GetUsers()
{
Users u = new Users();
u.ReadXml(Config.GetUserListPhysicalPath());
return u;
}
public static void SetUsers(Users u)
{
u.WriteXml(Config.GetUserListPhysicalPath());
}
...
}
Authentication
Forms Authentication is used, but I can imagine that Windows Authentication
would also work.
First, in the Login Module, your credentials are validated. This is done through
a helper method.
Login.ascx
void OnLogin(object sender, EventArgs args)
{
if(Portal.UserManagement.Login(account.Text, password.Text))
{
Response.Redirect(Request.RawUrl);
}
else
{
lError.Text = "Invalid Login";
}
}
UserManagement.cs
public static bool Login(string account, string password)
{
Users u = GetUsers();
Users.UserRow user = u.User.FindBylogin(account.ToLower());
if(user == null) return false;
if(user.password != password) return false;
FormsAuthentication.SetAuthCookie(account, false);
return true;
}
When a Http-Request occurs, Global.Application_AuthenticateRequest
is
called. There the user roles are set.
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if(Request.IsAuthenticated)
{
string[] roles = UserManagement.GetRoles(
HttpContext.Current.User.Identity.Name);
HttpContext.Current.User = new GenericPrincipal(
HttpContext.Current.User.Identity, roles);
}
}
UserManagement.cs
public static string[] GetRoles(string account)
{
Users u = GetUsers();
Users.UserRow user = u.User.FindBylogin(account.ToLower());
if(user == null) return new string[0];
Users.UserRoleRow[] roles = user.GetUserRoleRows();
string[] result = new string[roles.Length];
for(int i=0;i<roles.Length;i++)
{
result[i] = roles[i].RoleRow.name;
}
return result;
}
Loading Controls Dynamically
The PortalTab.ascx Web User Control renders the Tabs. The current Tab
reference is passed as a URL Parameter. Modules are loaded in the OnInit
Event so they can process their OnLoad
Events.
public abstract class PortalTab : System.Web.UI.UserControl
{
protected HtmlTableCell left;
protected HtmlTableCell middle;
protected HtmlTableCell right;
private void RenderModules(HtmlTableCell td, PortalDefinition.Tab tab,
ArrayList modules)
{
if(modules.Count == 0)
{
td.Visible = false;
return;
}
foreach(PortalDefinition.Module md in modules)
{
if(UserManagement.HasViewRights(Page.User, md.roles))
{
md.LoadModuleSettings();
Module m = null;
if(md.moduleSettings == null)
{
m = (Module)LoadControl(Config.GetModuleVirtualPath(md.type)
+ md.type + ".ascx");
}
else
{
m = (Module)LoadControl(Config.GetModuleVirtualPath(md.type)
+ md.moduleSettings.ctrl);
}
m.InitModule(tab.reference, md.reference,
Config.GetModuleVirtualPath(md.type),
UserManagement.HasEditRights(Page.User, md.roles));
if(m.IsVisible())
{
ModuleHeader mh = (ModuleHeader)LoadControl("ModuleHeader.ascx");
mh.SetModuleConfig(md);
td.Controls.Add(mh);
HtmlGenericControl div = new HtmlGenericControl("div");
div.Attributes.Add("class", "Module");
div.Controls.Add(m);
td.Controls.Add(div);
}
}
}
}
override protected void OnInit(EventArgs e)
{
PortalDefinition.Tab tab = PortalDefinition.GetCurrentTab();
if(UserManagement.HasViewRights(Page.User, tab.roles))
{
RenderModules(left, tab, tab.left);
RenderModules(middle, tab, tab.middle);
RenderModules(right, tab, tab.right);
}
InitializeComponent();
base.OnInit(e);
}
...
}
HttpHandler
The HttpHandler
is used to "translate" URLs from "http://server/Portal/main.tab.ascx"
to "http://server/Portal/RenderTable.aspx?TabRef=main". This is
interesting if you want to analyze your Web Server Log Files.
To get this work you must add this to your web.config file
<system.web>
<httpHandlers>
<add verb="*" path="*.tab.aspx" type="Portal.TabHttpHandler, Portal" />
<httpHandlers>
<system.web>
".tab.aspx" is used as a extension, because otherwise you have to
reconfigure your IIS.
This HttpModule
does nothing else than a simple Server.Transfer
public class TabHttpHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
string path = context.Request.Url.AbsolutePath.ToLower();
string tabRef = path.Substring(path.LastIndexOf("/") + 1);
tabRef = tabRef.Substring(0, tabRef.LastIndexOf(".tab.aspx"));
Hashtable r = new Hashtable();
foreach(string key in context.Request.QueryString.Keys)
{
r[key] = context.Request[key];
}
r["TabRef"] = tabRef;
string url = Portal.API.Config.GetMainPage();
bool firstParam = true;
foreach(DictionaryEntry e in r)
{
if(firstParam)
{
url += "?";
firstParam = false;
}
else
{
url += "&";
}
url += e.Key.ToString() + "=" + e.Value.ToString();
}
context.Server.Transfer(url);
}
public bool IsReusable
{
get
{
return true;
}
}
}
Creating a Module
Creating a Module is simple. Just create a directory in the Modules directory
and put there the View- and Edit Web User Control. The Controls names must be
(or can be reconfigured) <ModuleName>.ascx and Edit<ModuleName>.ascx.
Implementation
Just derive form Portal.API.Module
or Portal.API.EditModule
and implement "IsVisible
" if necessary.
The Module Class provides some properties and methods which describes the
Module.
Current Module Settings
Name |
Description |
IsVisible |
Can be overridden. Tells the Portal Framework if the Module should be rendered
or not. |
TabRef |
The current Tab Reference. This is a unique string. |
ModuleRef |
The current Module Reference. The Module Reference is not necessarily unique.
TabRef + ModuleRef is unique. |
ModuleVirtualPath |
The virtual path to the Module. |
ModulePhysicalPath |
The physical path to the Module. |
BuildURL |
Build a URL to the current Page. Use this method to implement Modules that
needs URL Parameter. |
ModuleHasEditRights |
True if the current user has edit rights. |
Configuration
Each Module has the responsibility to store its configuration and state. The
Portal API provides some Helper Methods.
Name |
Description |
ModuleConfigFile |
Physical Path to the configuration file. (<ModulePhysicalPath>\Module_<ModuleRef>.config) |
ModuleConfigSchemaFile |
Physical Path to the configuration schema file. (<ModulePhysicalPath>\
Module_<ModuleRef>.configModule.xsd) |
ReadCommonConfig |
Reads (XML Deserialize) the common configuration file. |
ReadConfig |
Reads (XML Deserialize/Dataset) the configuration file. |
WriteConfig |
Writes (XML Serialize/Dataset) the configuration file. |
Furthermore each Module can a configure its control files. These settings are
stored in the "ModuleSettings.config" file.
<module>
<ctrl>Counter.ascx</ctrl>
<editCtrl>none</editCtrl>
</module>
The "ctrl" Tag defines the View Web User Control. The "editCtrl" Tag can contain
"none", which means: there is no Edit Control. E.g. the Login or HitCounter
Modules are using this.
Example (Simple Html Module)
This simple Module reads a .htm file and renders it into a DIV
Tag.
View Control Html.ascx:
<%@ Control Language="c#" Inherits="Portal.API.Module" %>
<%@ Import namespace="System.IO" %>
<script runat="server">
private string GetPath()
{
return ModulePhysicalPath + ModuleRef + ".htm";
}
void Page_Load(object sender, EventArgs args)
{
if(File.Exists(GetPath()))
{
FileStream fs = File.OpenRead(GetPath());
StreamReader sr = new StreamReader(fs);
content.InnerHtml = sr.ReadToEnd();
fs.Close();
}
}
</script>
<div id="content" runat="server">
</div>
Edit Control EditHtml.ascx:
<%@ Control Language="c#" autoeventwireup="true"
Inherits="Portal.API.EditModule" %>
<%@ Import namespace="System.IO" %>
<script runat="server">
private string GetPath()
{
return ModulePhysicalPath + ModuleRef + ".htm";
}
void Page_Load(object sender, EventArgs args)
{
if(!IsPostBack)
{
if(File.Exists(GetPath()))
{
FileStream fs = File.OpenRead(GetPath());
StreamReader sr = new StreamReader(fs);
txt.Text = sr.ReadToEnd();
fs.Close();
}
}
}
void OnSave(object sender, EventArgs args)
{
FileStream fs = null;
try
{
fs = new FileStream(GetPath(), FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.None);
fs.SetLength(0);
StreamWriter sw = new StreamWriter(fs);
sw.Write(txt.Text);
sw.Close();
}
finally
{
if(fs != null)
{
fs.Close();
}
}
RedirectBack();
}
</script>
<asp:TextBox id="txt" Width="100%" Height="300px"
TextMode="MultiLine" Runat="server"></asp:TextBox>
<asp:LinkButton CssClass="LinkButton" runat="server" OnClick="OnSave">
Save & Back</asp:LinkButton>
Work from others
Not all work is from me. I took some code from others and made Modules. Thanks!
-
TreeCtrl
-
ImageBrowser
-
HitCounter
-
HtmlEditor
-
by (?) - No Name provided
-
Project Name: Yet Another HTML Editor for .Net
-
Found at gotdotnet.com (http://www.cwebrun.com/)
-
Forum
History
-
17.10.2003 Version 1.0.2 uploaded. TreeWebControlPrj is now in the zip
file.