Architecture


This document contains an overview of the architecture of the ASP.NET Community Starter Kit. The first part of this document focuses on two distinctive features of this Starter Kit: Skins and Database Generated Pages and Directories. You learn how these two features are implemented.

The second part of this document discusses methods of modifying the Community Starter Kit. First, you learn how to create new themes in order to modify the appearance of a community site. Next, you learn how to build custom modules, such as a Jobs module, that will work with the Community Starter Kit.

Skins

The Community Starter Kit is based on the ASP.NET Forums Application (available at www.ASP.net). Like the ASP.NET Forums Application, the Community Starter Kit uses skins to divide the user interface from the code.

There are four types of skins used with the Community Starter Kit:

Whenever you load a Community Starter Kit page, a number of skins are loaded that determine the appearance of the page. For example, if you select the Robotico theme for your community, and you request the Login page, the following skins are loaded (this isn't a complete list): What makes the story more complicated is the fact that skins support inheritance. If a skin cannot be loaded for a particular theme, the skin is loaded from the Default theme instead. This is important since it means that you do not need to create a skin for each and every page and control when you create a new theme. You can override only those skins that you want to modify.

A skin is implemented as an ASP.NET user control that contains only HTML content and ASP.NET controls (no code). For example, the Users_Login.ascx skin for the Login page looks like this:


<%@ Control %>
<p>
Login
</p>
Please enter your username and password below.
<br>
If you are a new user, click <a href="Users_Register.aspx">here</a> to register.

<asp:Panel id="pnlInvalidUsername" Runat="Server">
	The username you entered is invalid!
</asp:Panel>

<asp:Panel id="pnlInvalidPassword" Runat="Server">
	The password you entered is invalid!
</asp:Panel>

<table>
<tr>
	<td>Username:</td>
	<td><asp:TextBox id="txtUsername" Columns="20" runat="server" /></td>
</tr>
<tr>
	<td>Password:</td>
	<td><asp:TextBox id="txtPassword" Columns="20" TextMode="Password" runat="server" /></td>
</tr>
<tr>
	<td colspan="2"><asp:Button id="btnLogin" runat="server" Text="Login" /></td>
</tr>
</table>

<p>
Click <asp:HyperLink id="lnkPasswordReminder" Runat="Server" Text="here" /> for a password reminder.
</p>


Notice that the skin just contains HTML and ASP.NET controls such as TextBox controls.

All the pages in the Community Starter Kit are implemented as classes that ultimately inherit from the SkinnedCommunityControl base class. The SkinnedCommunityControl base class takes care of all the ugly details of loading the right skin for a control.

The SkinnedCommunityControl class has one important property and one important method that you must override: the SkinType property and the InitializeSkin method. The SkinType property indicates the type of skin associated with the control: ContentSkin, ControlSkin, or TemplateSkin. The InitializeSkin method is used to retrieve all the controls from the skin so that the properties of the controls can be manipulated in the code.

For example, the class that corresponds to the Login page (The Login.vb or Login.cs class) looks like this:
Login.vb

Imports System
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports ASPNET.StarterKit.Communities
Imports System.Web.Security


'*********************************************************************
'
' Login Class
'
' Represents the user login page which enables
' users to login to the community.
'
'*********************************************************************

Public Class Login
    Inherits SkinnedCommunityControl
    
    Private _skinFileName As String = "Users_Login.ascx"
    
    Private txtUsername As TextBox
    Private txtPassword As TextBox
    Private chkPersist As CheckBox
    Private btnLogin As Button
    Private pnlInvalidUsername As Panel
    Private pnlInvalidPassword As Panel
    Private lnkPasswordReminder As HyperLink
    
    
    
    
    '*********************************************************************
    '
    ' Login Constructor
    '
    ' Calls the base SkinnedCommunityControl constructor
    ' and assigns the default page skin.
    '
    '*********************************************************************
    Public Sub New()
        ' Assign a default skin file name
        If SkinFileName Is Nothing Then
            SkinFileName = _skinFileName
        End If
    End Sub 'New 
    
    '*********************************************************************
    '
    ' SkinType Property
    '
    ' Specifies the skins directory where this page's skin file is located.
    '
    '*********************************************************************
    
    Protected Overrides ReadOnly Property SkinType() As String
        Get
            Return "ContentSkins"
        End Get
    End Property
     
    
    '*********************************************************************
    '
    ' InitializeSkin Method
    '
    ' Retrieves all the controls from the Page Skin
    '
    '*********************************************************************
    Protected Overrides Sub InitializeSkin(skin As Control)
        ' Find the Username TextBox
        txtUsername = CType(GetControl(skin, "txtUsername"), TextBox)
        
        ' Find the Password TextBox
        txtPassword = CType(GetControl(skin, "txtPassword"), TextBox)
        AddHandler txtPassword.TextChanged, AddressOf btnLogin_Click
        
        ' Find the Persist Checkbox (Optional)
        chkPersist = CType(GetOptionalControl(skin, "chkPersist"), CheckBox)
        
        ' Find the Invalid Username Panel
        pnlInvalidUsername = CType(GetControl(skin, "pnlInvalidUsername"), Panel)
        pnlInvalidUsername.Visible = False
        
        ' Find the Invalid Password Panel
        pnlInvalidPassword = CType(GetControl(skin, "pnlInvalidPassword"), Panel)
        pnlInvalidPassword.Visible = False
        
        ' Find Login Button
        btnLogin = CType(GetControl(skin, "btnLogin"), Button)
        AddHandler btnLogin.Click, AddressOf btnLogin_Click
        
        ' Find password reminder
        lnkPasswordReminder = CType(GetControl(skin, "lnkPasswordReminder"), HyperLink)
        lnkPasswordReminder.NavigateUrl = "~/Users_PasswordReminder.aspx"
    End Sub 'InitializeSkin
    
    
    
    '*********************************************************************
    '
    ' btnLogin_Click Method
    '
    ' Checks username and password against database. If 
    ' everything checks, logins the user.
    '
    '*********************************************************************
    Sub btnLogin_Click(s As [Object], e As EventArgs)
        Dim blPersist As Boolean = False
        
        ' Determine whether password should be persisted
        If Not (chkPersist Is Nothing) Then
            blPersist = chkPersist.Checked
        End If 
        ' Either login or display error message
        Select Case UserUtility.LoginUser(txtUsername.Text, txtPassword.Text)
            Case 0 ' Success!
                FormsAuthentication.SetAuthCookie(txtUsername.Text, blPersist)
                Dim redirectUrl As String = FormsAuthentication.GetRedirectUrl(txtUsername.Text, blPersist).ToLower()
                If redirectUrl.IndexOf("users_logout.aspx") = - 1 Then
                    Context.Response.Redirect(redirectUrl)
                Else
                    Context.Response.Redirect(CommunityGlobals.ResolveBase("default.aspx"))
                End If
            Case 1 ' Invalid Password
                pnlInvalidPassword.Visible = True
                pnlInvalidUsername.Visible = False
            Case 2 ' Invalid Username
                pnlInvalidUsername.Visible = True
                pnlInvalidPassword.Visible = False
        End Select
    End Sub 'btnLogin_Click
End Class 'Login


 

Login.cs

namespace ASPNET.StarterKit.Communities {

    using System;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using ASPNET.StarterKit.Communities;
    using System.Web.Security;

    //*********************************************************************
    //
    // Login Class
    //
    // Represents the user login page which enables
    // users to login to the community.
    //
    //*********************************************************************

    public class Login : SkinnedCommunityControl {
    
        string _skinFileName = "Users_Login.ascx";

        TextBox txtUsername;
        TextBox txtPassword;
        CheckBox chkPersist;
        Button btnLogin;
        Panel pnlInvalidUsername;
        Panel pnlInvalidPassword;
        HyperLink lnkPasswordReminder;



        //*********************************************************************
        //
        // Login Constructor
        //
        // Calls the base SkinnedCommunityControl constructor
        // and assigns the default page skin.
        //
        //*********************************************************************

		public Login() : base() {
            // Assign a default skin file name
            if (SkinFileName == null)
                SkinFileName = _skinFileName;
        }
        

        //*********************************************************************
        //
        // SkinType Property
        //
        // Specifies the skins directory where this page's skin file is located.
        //
        //*********************************************************************
        
        override protected string SkinType {
            get { return "ContentSkins"; }
        }


        //*********************************************************************
        //
        // InitializeSkin Method
        //
        // Retrieves all the controls from the Page Skin
        //
        //*********************************************************************

        override protected void InitializeSkin(Control skin) {
            // Find the Username TextBox
            txtUsername = (TextBox)GetControl(skin, "txtUsername");

            // Find the Password TextBox
            txtPassword = (TextBox)GetControl(skin, "txtPassword");
            txtPassword.TextChanged += new System.EventHandler(btnLogin_Click);
 
            // Find the Persist Checkbox (Optional)
            chkPersist = (CheckBox)GetOptionalControl(skin, "chkPersist");
            
            // Find the Invalid Username Panel
            pnlInvalidUsername = (Panel)GetControl(skin, "pnlInvalidUsername");
            pnlInvalidUsername.Visible = false;
            
            // Find the Invalid Password Panel
            pnlInvalidPassword = (Panel)GetControl(skin, "pnlInvalidPassword");
            pnlInvalidPassword.Visible = false;
                        
            // Find Login Button
            btnLogin = (Button)GetControl(skin, "btnLogin");
    		btnLogin.Click += new System.EventHandler(btnLogin_Click);

            // Find password reminder
            lnkPasswordReminder = (HyperLink)GetControl(skin, "lnkPasswordReminder");
            lnkPasswordReminder.NavigateUrl = "~/Users_PasswordReminder.aspx";
        } 

        
        //*********************************************************************
        //
        // btnLogin_Click Method
        //
        // Checks username and password against database. If 
        // everything checks, logins the user.
        //
        //*********************************************************************
        
        void btnLogin_Click(Object s, EventArgs e) {
            bool blPersist = false;
            
            // Determine whether password should be persisted
            if (chkPersist != null)
                blPersist = chkPersist.Checked;
            
            // Either login or display error message
            switch ( UserUtility.LoginUser(txtUsername.Text,txtPassword.Text) ) {
                case 0: // Success!
                    FormsAuthentication.SetAuthCookie(txtUsername.Text, blPersist);
                    string redirectUrl = FormsAuthentication.GetRedirectUrl(txtUsername.Text, blPersist).ToLower();
                    if (redirectUrl.IndexOf("users_logout.aspx") == -1)
                        Context.Response.Redirect(redirectUrl);
                    else
                        Context.Response.Redirect(CommunityGlobals.ResolveBase("default.aspx"));
                    break;
                case 1: // Invalid Password
                    pnlInvalidPassword.Visible = true;
                    pnlInvalidUsername.Visible = false;
                    break;
                case 2: // Invalid Username
                    pnlInvalidUsername.Visible = true;
                    pnlInvalidPassword.Visible = false;
                    break;
            }    
        }       
 


    }
}

The SkinType property of the Login class identifies the skin associated with the class as a Content Skin. This means the control will attempt to load the skin from the ContentSkins subdirectory of the Themes folder.

The InitializeSkin method retrieves all of the controls from the Content Skin using the GetControl() method. The GetControl() method is a method of the base SkinnedCommunityControl class that retrieves a particular control from a skin. In particular, the InitializeSkin method retrieves the txtUsername and txtPassword TextBoxes and the btnLogin Button from the content skin.

After the btnLogin Button has been retrieved from the Content Skin, the Button is associated with the btnLogin_Click event handler. When someone clicks the Login button, the btnLogin_Click method executes and the user is validated against the database.

All of the pages in the Community Starter Kit are implemented in the same way as the Login page. Each page consists of a class and a skin. The skin determines the appearance of the page. The class contains all the functionality of the page.

All pages in the Community Starter Kit implement the InitializeSkin method in order to load controls from the content skin associated with the page. After the controls have been loaded from the skin, the properties of the controls can be modified.

The advantage of this method of creating pages is that the appearance of a page can be dramatically altered simply by associating a different skin with the page. When you select a different theme -- such as Arc, Robotico, Professional -- different skins are associated with the pages and the appearance of your community site is transformed.

Database Generated Pages and Directories

There is really only one .aspx page in the whole Community Starter Kit. The page is named the CommunityDefault.aspx page and it is located in the root directory. The CommunityDefault.aspx page is responsible for displaying every other page that is requested regardless of the community the page is requested from.

All pages and all directories in the Community Starter Kit are dynamically generated from database tables. Whenever you request a page, an HTTP Module named the Communities Module handles the request. This module performs the following actions:

  1. It determines the community associated with the page request.
  2. It determines the section associated with the page request.
  3. It determines the page associated with the page request.
  4. It determines the user associated with the page request.
  5. It stores the community ID, section information, page information, and user information.
  6. It redirects the request, using the Context.RewritePath() method, to the CommunityDefault.aspx page.

The CommunityDefault.aspx page uses the information collected in the Communities Module to generate and display the proper page.

The CommunityInfo, SectionInfo, PageInfo and UserInfo Objects

The Communities module stores the community information, section information, page information, and user information in the Items collection of the HttpContext object. This means that this information can be retrieved within every class and control during the lifetime of the page request.

The community information is represented with a class named CommunityInfo. The CommunityInfo class represents the name of the community.

The section information is represented with a class named SectionInfo. The SectionInfo class contains properties such as Title, Path, Description, ViewRoles that represent all the information about a particular section.

The page information is represented with a class named PageInfo. This class contains properties that represent such values as the type of content displayed by the page (for example, an article or book).

Finally, user information is represented with the UserInfo object. The UserInfo object contains properties that represent the user's username and the roles and permissions associated with the user. For example, the UserInfo object has a property named MayAdd that determines whether or not the user can add content to a section.

You can retrieve these objects within any control or class by using the following code:

C#

CommunityInfo objCommunityInfo =

       (CommunityInfo)System.Web.HttpContext.Current.Items["CommunityInfo"];

 

SectionInfo objSectionInfo =

       (SectionInfo)System.Web.HttpContext.Current.Items["SectionInfo"];

 

PageInfo objPageInfo = 

       (PageInfo)System.Web.HttpContext.Current.Items["PageInfo"];

 

UserInfo objUserInfo = 

       (UserInfo)System.Web.HttpContext.Current.Items["UserInfo"];

VB.NET

Dim objCommunityInfo As CommunityInfo =

       CType(System.Web.HttpContext.Current.Items("CommunityInfo"), CommunityInfo)

 

Dim objSectionInfo As SectionInfo =

       CType(System.Web.HttpContext.Current.Items("SectionInfo"), SectionInfo)

 

Dim objPageInfo As PageInfo =

       CType(System.Web.HttpContext.Current.Items("PageInfo"), PageInfo)

 

Dim objUserInfo As UserInfo =

       CType(System.Web.HttpContext.Current.Items("UserInfo"), UserInfo)

Community Starter Kit Page Types

The Community Starter Kit uses three types of pages:

Community, page and section information is stored in the following database tables: When the Community Starter Kit application starts, it loads the Community_Communies, Community_Sections, and Community_NamedPages database tables into memory. The Communities Module performs lookups on these in-memory database tables with each page request to retrieve information about the section and page being requested.

If you request a content page, such as a particular article or photo, the Communities module will perform a database lookup to get the required information.

Modifying the Community Starter Kit

All the source code for the Community Starter Kit is included when you download the application. You can modify both the themes and code included with the Starter Kit for whatever purpose you desire.

Creating New Themes

Creating a new theme to use with the Community Starter Kit is easy. Simply take any existing theme, such as the Arc theme, copy it to a new directory, and modify the skins. As soon as you add the new folder, the theme will appear in the Edit Sections page.

All Themes are located beneath the Communities folder. The Communities folder has the following structure:

 
\Communities

	\Common
		\Themes


	\Community1
		\Themes


	\Community2
		\Themes
The Communities\Common folder contains themes which can be used by any community. Individual communities can have their own private Themes folder that contains themes that can only be used with that community.

A Theme folder has the following structure:

\Themes
	\Default
		\Skins
			\ContentSkins
			\ControlSkins
			\PageSkins
			\TemplateSkins

		\Styles
The skin that determines the overall page layout for a theme is located in the PageSkins subfolders and it is always named Default.ascx. You can modify this skin to change the number of columns in a page, the location of the section and topic menus, or anything else about the general layout of a page.

If you want to alter the appearance of a particular page, then you need to override the default content skin for the page by adding a skin to the ContentSkins folder in your new theme. For example, if you want to modify the appearance of the Login.aspx page, then you can copy the Users_Login.ascx file from the ContentSkins folder in the Default theme into the ContentSkins folder of your new theme.

You do not need to create a content skin or control skin for each skin in the Default theme. If you do not supply a skin, the skin from the Default theme will be used instead.

The Styles folder located beneath the Themes folder contains one or more style sheets that can be applied to the theme. Any style sheets (files that end with the .css extension) added to the Styles folder with automatically appear in the Styles dropdown list in the Edit Site page.

Creating New Modules

You can add new modules to the Community Starter Kit. For example, you might want to create a new module that displays job listings (a Jobs module). The basic steps required to create a new module are the following:

For example, when creating a Jobs module, you would most likely want to include the following pages: To create these pages, you would start by creating four classes. The Community Starter Kit Framework includes base classes that correspond to each of these page types: These four base classes are located in the \Engine\Framework\BaseClasses directory.

For samples of how to implement these classes, see the source code of the modules included with the Community Starter Kit. For example, the Books module includes a BookSection, Book, AddBook, and EditBook class.

Next, you need to create skins for these classes. You'll need to add the following skins to the ContentSkins folder in the Default theme:

Notice that only three skins are listed. All the standard modules in the Community Starter Kit use the same skin for both the Add and Edit page.

For examples of content skins, see the content skins used for the Books Module. The Books Module includes the following skins (which you can find in the \Communities\Common\Themes\Default\Skins\ContentSkins folder):

After you have created the pages, you need to register them in the database before they will appear. You need to add entries to both the Community_NamedPages and Community_PageTypes database tables.

To view the SQL statements used to register the pages in the Books section, see the Community_MaintenanceInitializeBooks stored procedure. For example, this stored procedure registers the BookSection page with the following INSERT statement:


IF NOT EXISTS (SELECT * FROM Community_PageTypes WHERE pageType_Name='Book Section')

BEGIN  

  INSERT INTO Community_PageTypes 

  (

    pageType_Name,

    pageType_Description,

    pageType_PageContent,

    pageType_isSectionType

  ) 

  VALUES

  (

    'Book Section',

    'Contains book listings',

    'ASPNET.StarterKit.Communities.Books.BookSection',

    1

  )

END

ELSE

  PRINT 'WARNING: The Books Module has already been registered.'

 

After this statement is executed, the Books module appears as one of the Content Types that you can select when adding or editing a section by using the Edit Section or Add Section page in the Admin pages.


Back to Documentation Index