Audio Video Modules for ASP.NET Community Starter Kit : Part I

Welcome Abstract:

In this article, we will build an additional module for ASP.net CommunityStarterKit that enables you to publish audio and video contents. This module works together with Windows Media Services and it is written in C#. You have to install CommunityStarterKit (C# version for VS) to follow this article.

Live Display: http://www.abraham-consulting.biz/ACPortal/AudioVideoGallary/default.aspx
Source Code: 
http://www.paul-abraham.com/ArticleDP/AlterScript.zip 

1. Introduction #

ASP.net Community Starter Kit(CSK) contents 9 standard modules such as Articles, Events, Books, Links, Downloads, Discuss and Photo Gallery. These modules enable community members to add , update or delete their contributions to a community. We presume that you have already installed CommunityStarterKit on your machine and understood the architecture of CommunityStarterKit. If you havent already installed it, then you can down load it from here (
http://www.asp.net/StarterKits/DownloadCommunity.aspx?tabindex=0&tabid=1). The Community Starter Kit(CSK) is well documented and its architecture section describes how it works, but it briefly explains how to add new modules and leaves this aspect as an exercise to user. In this article we will design and implement audio-video module that fits perfectly in the CSK architecture. It is a very useful community module, because there are lot of things in the world which you cant display or describe only through words and static images. For example, you can use a video clip to discribe how to install a SSL certificate on a web server or you can use an audio clip to explain a musical composition; say, Bachs Mass in b minor.

In addition, I like to recommend to deal with the architecture of Community Starter Kit, because it has very interesting application architecture. The following points demonstrate its revolutionary character.

  1. Its business entity components are designed according to domain-model
    (Refer Martin Fowler :page 116)

  2. It has only one physical .aspx page to display all kind of contents of a community.

  3. you can change dramatically the entire physical layout of the community with a single mouse click.

    We will now install our Audio-Video module to the Community Starter Kit and I assume again that you already installed the CSVS on your machine.

  4. Download the AlterScript.zip file and it consists two subfolders :AudioVideos and Skins , a sql file: DBAlterScript.sql, and two images ( video.gif and Audio.gif).

  5. Execute the script DBAlterScript.sql against the database CommunityStarterKit in order to create necessary table and store procedures for the audio-video module.

  6. Copy the subfolder AudioVideos from the downloaded folder and paste it under the folder ..\CommunityStarterKit\Engine\Modules \ as its subfolder and add it to the Visual Studio(push the button Show All Files on the Solution Explorer and then right mouse click on the subfolder AudioVideos  Add To Project)

  7. Copy the all four .ascx files of the Skins subfolder and add it to the folder \CommunityStarterKit\Communities\Common\Themes\Default\Skins\ContentSkins and add these files to the VS . Because of the .ascx file type, VS will ask you whether to create code behind for these .ascx files or not. Please select the No button in this case.

  8. Copy the images Audio.gif and Video.gif and paste it to the folder \CommunityStarterKit\Communities\Common\Images.

I hope that you can compile and use the Audio-Video Module .Now, we will sum up the CSK architecture and recall how it works.(Please refer the CSK Architecture Documentation, if the following section too brief)

2. Architecture of Community Starter Kit

We will discuss in this section how a request from a user processed in the CSK. In coming request will be firstly incepted by the CommunitiesModule HttpModule. This module will parse the requested url and retrieve all needed information about the community , section , page and user from the database and saves it in to the current HttpContext .Items collection. As you know that CSK has really one .aspx page named CommunityDefault.aspx. These saved information in the current HttpContext are used in the CommunityDefault.aspx page to assemble required content piece by piece.

Finally , the CommunitiesModule HttpModule will redirect the request internally to the CommuntityDefaultPage.aspx by using the HttpContext.RewritePath method . Because of the internal redirection, users wont get the impression that the requested pages and directories are virtual and generated from the stored information.

The void CommunitiesModule.Application_BeginRequest (see figure 1) method implements the above described initialization functionality. The PageInfo class contents all sufficient information to assemble a requested page and as you can see in the CommunitiesModule GetPageInfo method that the instance of PageInfo class is created depend from requested page type . We have three page types in CSK.

  • Section Default Page: This page type appears when you open a section page like Books or Events. CSK loads a list of enabled sections and saves it in the HttpContext.Cache (see SectionCollection SectionUtility.GetAllEnabledSections()); therefore, CSK doesnt need query the database always in order to create an instance of the PageInfo class for these type of pages.
  • Content Pages: This page type appears when you want to inform about a particular book or an event and CSK doesnt save any information about this page in the cache and uses the PageInfo ContentPageUtility.GetPageInfoFromPageContentID method to retrieve from the database.

  • Name Pages: This page type appears when you want update a certain book or event .CSK loads a list of all named pages in the cache (see NamedPageCollection NamedPageUtilityGetAllNamedPages() method )

Spoken in the language of design patterns, we can consider the CommunitiesModule HttpModule as an interception filter(( http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpatterns/html/DesInterceptingFilter.asp ). I think that this type of architecture (interception filter + database generated files/directories) is a genius way to by-pass applying front controller
(
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpatterns/html/DesFrontController.asp )

namespace ASPNET.StarterKit.Communities
{
using
System;
using
System.Web;
using
System.Globalization;
using
System.Threading;
using System.IO;

// CommunitiesModule Class
// This HTTP module translates all requests for pages
// into requests for the community default page. It also initializes
// CommunityInfo, SectionInfo, UserInfo and (optionally) PageInfo objects for
// each request and places these objects into the Context.
public class
CommunitiesModule : IHttpModule
{
//*********************************************************************
// Init Method
// Associates handlers with the BeginRequest and AuthenticateRequest
// events.
//*********************************************************************
public void
Init(HttpApplication application)
{
application.BeginRequest += (
new EventHandler(this
.Application_BeginRequest));
application.AuthenticateRequest += (
new EventHandler(this
.Application_AuthenticateRequest));
}
//*********************************************************************
// Application_BeginRequest Method
// Determines the Community, Section, and Page associated
// with each request.
//*********************************************************************
private void
Application_BeginRequest(Object source, EventArgs e)
{
// Get abbreviated reference to current context
HttpContext Context = HttpContext.Current;
// set culture based on acceptlanguage
if (Context.Request.UserLanguages != null
)
{
try

{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(Context.Request.UserLanguages[0]);
}
catch
{}
}
// Retrieve path
string
requestPath = CommunityGlobals.RemovePathInfo(Context.Request.Path.ToLower());
string
requestBasePath = CommunityGlobals.GetSectionPath(requestPath);
// Figure out the community and add to Context
CommunityInfo objCommunityInfo = CommunityUtility.GetCommunityInfo();
if (objCommunityInfo == null
)
return
;
Context.Items[ "CommunityInfo" ] = objCommunityInfo;
// Figure out the section and add to context
SectionInfo objSectionInfo = SectionUtility.GetSectionInfoFromPath(requestBasePath);
if (objSectionInfo == null
)
return
;
Context.Items[ "SectionInfo" ] = objSectionInfo;
// Is this a Web Service request?
if
(requestPath.EndsWith(".asmx"))
{
Context.RewritePath(CommunityGlobals.UrlBaseService + Context.Request.PathInfo);
return
;
}
// Figure out the page and add to Context
if
(requestPath.EndsWith(".aspx"))
{
PageInfo objPageInfo = GetPageInfo(objSectionInfo, requestPath);
if (objPageInfo != null
)
{
Context.Items[ "PageInfo" ] = objPageInfo;
Context.RewritePath(CommunityGlobals.UrlBasePage);
}
}
}
//*********************************************************************
// GetPageInfo Method
// Retrieves the PageInfo associated with the page begin
// being requested.
//*********************************************************************
private PageInfo GetPageInfo(SectionInfo objSectionInfo, string
requestPath)
{
// Get Request Parts
string
requestFile = CommunityGlobals.GetPageName(requestPath);
PageInfo objPageInfo =
null
;
// Check for section default page
if
(requestFile.ToLower() == "/default.aspx")
return new
PageInfo
(
-1,
objSectionInfo.Name,
objSectionInfo.ParentSectionID,
objSectionInfo.PageType,
objSectionInfo.Title,
objSectionInfo.Description,
objSectionInfo.PageMetaDesc,
objSectionInfo.PageMetaKeys,
objSectionInfo.Content
);
// Check for named page
objPageInfo = NamedPageUtility.GetPageInfoFromNamedPage(requestFile);
if (objPageInfo != null
)
return
objPageInfo;
// Check for content page
objPageInfo = ContentPageUtility.GetPageInfoFromPageContentID(requestFile, objSectionInfo);
if (objPageInfo != null
)
return
objPageInfo;
// If everything fails, just return null
return null
;
}
//*********************************************************************
// Application_AuthenticateRequest Event
// If the client is authenticated with the application, then determine
// which security roles he/she belongs to and replace the "User" intrinsic
// with a custom IPrincipal security object that permits "User.IsInRole"
// role checks within the application
// Roles are cached in the browser in an in-memory encrypted cookie. If the
// cookie doesn't exist yet for this session, create it.
//*********************************************************************
void
Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpContext Context = HttpContext.Current;
// Get the user roles
UserUtility.GetUserRoles();
// Get SectionInfo
SectionInfo objSectionInfo = (SectionInfo)Context.Items[ "SectionInfo" ];
if (objSectionInfo == null
)
return
;
// Create the User Info Object
UserInfo objUserInfo = new
UserInfo(objSectionInfo);
Context.Items[ "UserInfo" ] = objUserInfo;
// Make sure the user can view the page
if
(!objUserInfo.MayView)
CommunityGlobals.ForceLogin();
}
/
/*********************************************************************
// Dispose Method
// This method is required by the HttpModule Interface.
//*********************************************************************
public void
Dispose()
{}
}

Figure 1 source code for CommunitesModule

Now, we look what happens in the communityDefault class which is the code-behind class for the communitydefault.aspx page . The void Page_Init(Object s, EventArgs e) method of this class builds literally the requested page(see figure 2) . I like to outline the highlights of this method step by step

  1. The original requested url will be rewritten using the HttpContext.RewritePath method

  2. Retrieval of PageInfo and SectionInfo objects from the HttpContext.Items collection.

  3. A html skeleton will be created to hold the contents.

  4. A page skin "/Communities/+style name+ /Skins/PageSkins/Default.ascx" , which is depend from the community style(style (e.g. Arc,Robertico or Default) , will be loaded Depend from the page

  5. The PageInfo.content property represents class name of the requested content. The Activate.CreateInstance method will be used to create the requested class and the created class will be implanted in to the placeholder control(Content).

// Add the Page Content Page Part
void Page_Init(Object s, EventArgs e)
{
Control objPageSkin =
null
;
PlaceHolder objPagePart =
null
;
// Rewrite Path Back to Original
Context.RewritePath( Path.GetFileName( Request.RawUrl ) );
// Get the PageInfo and SectionInfo objects
PageInfo objPageInfo = (PageInfo)Context.Items[ "PageInfo" ];
SectionInfo objSectionInfo = (SectionInfo)Context.Items[ "SectionInfo" ];
// Create a StringBuilder to build up the Page Head
StringBuilder objBuilder = new
StringBuilder();
objBuilder.Append( "<html>\n<head>" );
// Add the Page Title
objBuilder.AppendFormat( "\n<title>{0}</title>", objPageInfo.Title );
// Add the Page Style
objBuilder.AppendFormat( "\n<link href=\"{0}\" type=\"text/css\" rel=\"stylesheet\"/>", CommunityGlobals.ResolveBase( "Communities" + "/" + objSectionInfo.Style ));
// Add the Meta Tags
objBuilder.AppendFormat( "\n<META NAME=\"KEYWORDS\" CONTENT=\"{0}\"/>", Server.HtmlEncode(objPageInfo.MetaKeys) );
objBuilder.AppendFormat( "\n<META NAME=\"DESCRIPTION\" CONTENT=\"{0}\"/>", Server.HtmlEncode(objPageInfo.MetaDesc) );
objBuilder.AppendFormat( "\n<META NAME=\"PAGETYPE\" CONTENT=\"{0}\"/>", objPageInfo.Content );
objBuilder.Append( "\n<META ASPNETSTARTERKIT=\"CommunitySK\" Version=\"CSVS 1.0\"/>" );
// Add the Page Head to the Page
objBuilder.Append( "\n</head>\n<body marginheight=\"0\" marginwidth=\"0\" leftmargin=\"0\" rightmargin=\"0\" class=\"pageBody\">\n" );
Controls.Add(
new
LiteralControl( objBuilder.ToString() ) );
// Create a Form Control
HtmlForm objForm = new
HtmlForm();
objForm.ID = "PageForm";
// Load the Page Skin
objPageSkin = LoadControl( CommunityGlobals.AppPath + "/Communities/" + objSectionInfo.Skin + "/Skins/PageSkins/Default.ascx" );
// Add the Page Content Page Part
objPagePart = (PlaceHolder)objPageSkin.FindControl( "content" );
if (objPagePart != null
)
{
Control objPageContent = (Control)Activator.CreateInstance( Type.GetType( objPageInfo.Content,
true
) );
//Control objPageContent = LoadControl( CommunityGlobals.AppPath + "/Modules" + objPageInfo.PageContent );
objPagePart.Controls.Add( objPageContent );
}
// Find Search
TextBox txtSearch = (TextBox)objPageSkin.FindControl("txtSearch");
if (txtSearch != null
)
txtSearch.TextChanged +=
new
EventHandler(RedirectToSearchPage);
LinkButton lnkSearch = (LinkButton)objPageSkin.FindControl("lnkSearch");
if (lnkSearch != null
)
lnkSearch.CausesValidation =
false
;
// Add the Page Footer
objPagePart = (PlaceHolder)objPageSkin.FindControl( "Footer" );
if (objPagePart != null
)
objPagePart.Controls.Add(
new
LiteralControl( objSectionInfo.Footer ) );
// Add the Page Skin to the Form
objForm.Controls.Add( objPageSkin );
// Add the Form Control
Controls.Add( objForm );
// Close the Page
Controls.Add( new
LiteralControl( "\n</body>\n</html>" ) );
}

Figure 2 (This figure displays the method
void communityDefault.Page_Init)

continue article


Similar Articles