|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThis 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
BackgroundFirst 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 codeFeel free to use the Portal under the GPL License or code snippets freely. SummaryA 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:
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
ASPX-Pages are used as container for Web User Controls. They have hardly program logic. StorageTwo 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
{
// Static Serializer
private static XmlSerializer xmlPortalDef =
new XmlSerializer(typeof(PortalDefinition));
...
// Loads the Portal Definition
public static PortalDefinition Load()
{
// Create a Text Reader which reads the file
XmlTextReader xmlReader = new XmlTextReader(
Config.GetPortalDefinitionPhysicalPath());
// Deserialize
PortalDefinition pd = (PortalDefinition)xmlPortalDef.Deserialize(
xmlReader);
// Close the file
xmlReader.Close();
return pd;
}
// Saves the Portal Definition
public void Save()
{
// Create a Text Writer which writes the file
XmlTextWriter xmlWriter = new XmlTextWriter(
Config.GetPortalDefinitionPhysicalPath(), System.Text.Encoding.UTF8);
// Set Formating
xmlWriter.Formatting = Formatting.Indented;
// Serialize
xmlPortalDef.Serialize(xmlWriter, this);
// Close the file
xmlWriter.Close();
}
...
}
Users are stored in a public class UserManagement
{
...
// Reads the User Dataset from a file
public static Users GetUsers()
{
Users u = new Users();
u.ReadXml(Config.GetUserListPhysicalPath());
return u;
}
// Writes the User Dataset to a file
public static void SetUsers(Users u)
{
u.WriteXml(Config.GetUserListPhysicalPath());
}
...
}
AuthenticationForms 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.
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)
{
// Load Dataset
Users u = GetUsers();
// Find user
Users.UserRow user = u.User.FindBylogin(account.ToLower());
if(user == null) return false;
// Check password
if(user.password != password) return false;
// Set Authentication Cookie
FormsAuthentication.SetAuthCookie(account, false);
return true;
}
When a Http-Request occurs, protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if(Request.IsAuthenticated)
{
// Read users roles
string[] roles = UserManagement.GetRoles(
HttpContext.Current.User.Identity.Name);
// Set a new Principal with the proper roles
HttpContext.Current.User = new GenericPrincipal(
HttpContext.Current.User.Identity, roles);
}
}
UserManagement.cs
public static string[] GetRoles(string account)
{
// Load the User Dataset
Users u = GetUsers();
// Find the current user
Users.UserRow user = u.User.FindBylogin(account.ToLower());
if(user == null) return new string[0];
// Read users roles and add to a string array
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 DynamicallyThe PortalTab.ascx Web User Control renders the Tabs. The current Tab
reference is passed as a URL Parameter. Modules are loaded in the public abstract class PortalTab : System.Web.UI.UserControl
{
// Table Cells where the Modules are rendered
protected HtmlTableCell left;
protected HtmlTableCell middle;
protected HtmlTableCell right;
// Renders a Module Column
private void RenderModules(HtmlTableCell td, PortalDefinition.Tab tab,
ArrayList modules)
{
// Hide the cell if there where no Modules
if(modules.Count == 0)
{
td.Visible = false;
return;
}
foreach(PortalDefinition.Module md in modules)
{
// Only if the User has view rights
if(UserManagement.HasViewRights(Page.User, md.roles))
{
md.LoadModuleSettings();
// Initialize the Module
Module m = null;
// Load the Module
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);
}
// Initialize the Module
m.InitModule(tab.reference, md.reference,
Config.GetModuleVirtualPath(md.type),
UserManagement.HasEditRights(Page.User, md.roles));
// Each Module can decide if it wants to be rendered.
// The Login Module does so.
if(m.IsVisible())
{
// Add Module Header
ModuleHeader mh = (ModuleHeader)LoadControl("ModuleHeader.ascx");
mh.SetModuleConfig(md);
td.Controls.Add(mh);
// Add Module
HtmlGenericControl div = new HtmlGenericControl("div");
div.Attributes.Add("class", "Module");
div.Controls.Add(m);
td.Controls.Add(div);
}
}
}
}
// Called by the ASP.NET Framework
override protected void OnInit(EventArgs e)
{
PortalDefinition.Tab tab = PortalDefinition.GetCurrentTab();
// Check user rights
if(UserManagement.HasViewRights(Page.User, tab.roles))
{
// Render
RenderModules(left, tab, tab.left);
RenderModules(middle, tab, tab.middle);
RenderModules(right, tab, tab.right);
}
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
InitializeComponent();
base.OnInit(e);
}
...
}
HttpHandlerThe 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 public class TabHttpHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
// Parse the URL and extract the Tab Reference
string path = context.Request.Url.AbsolutePath.ToLower();
string tabRef = path.Substring(path.LastIndexOf("/") + 1);
// get "TabRef.tab"
tabRef = tabRef.Substring(0, tabRef.LastIndexOf(".tab.aspx"));
// get "TabRef"
// Save URL Parameter
Hashtable r = new Hashtable();
foreach(string key in context.Request.QueryString.Keys)
{
r[key] = context.Request[key];
}
r["TabRef"] = tabRef;
// Read the configuration to determinate the current main page
string url = Portal.API.Config.GetMainPage();
// Build the URL
bool firstParam = true;
foreach(DictionaryEntry e in r)
{
if(firstParam)
{
url += "?";
firstParam = false;
}
else
{
url += "&";
}
url += e.Key.ToString() + "=" + e.Value.ToString();
}
// Redirect
context.Server.Transfer(url);
}
public bool IsReusable
{
get
{
return true;
}
}
}
Creating a ModuleCreating 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. ImplementationJust derive form Current Module Settings
ConfigurationEach Module has the responsibility to store its configuration and state. The Portal API provides some Helper Methods.
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 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)
{
// Open file
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)
{
// Open file
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); // Truncate
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 othersNot all work is from me. I took some code from others and made Modules. Thanks!
History
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||