|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Part I - IntroductionThis is a journey on how to build a better Base page. The result of this will be a reusable framework that you can use to create as many Base pages as you like (on many different sites) and still have something that will keep the designers on your team happy. There is a lot to explain so I am breaking this up into several parts starting with the pros and cons of all the approaches I have seen in the past. Whenever I start a new ASP.NET project I am asked how to make a look and feel that can be reused on all pages of the site. The following are the several approaches I have seen to date: Have a designer create an HTML template and reuse it in all your pages
Comments: This is the approach I have seen done by folks breaking into ASP.NET development. Maintaining sites like this can be a nightmare to say the least. Just pray your client does not ask to change the main look and feel or you will be staying late to get it done… Create user controls and reuse them on all pages of the site
Comments: This is a little better as it makes use of User Controls to centralize the look and feel into separate components but can still be problematic and time consuming when the main look and feel templates need to be added/removed. Create a base page and hardcode the look and feel into the base page’s OnInit() call
Comments: We used this approach at two different client sites and it worked well. However when I presented this approach to the client I was at when I wrote this, they balked and wanted to know how to use the visual designer and also how to make changes without compiling and redeploying the site DLL. The next approach is the answer to that question. Create UserControls for all the sections of your main look and feel, and dynamically load them when your Base page is generated
Comments: Although this approach served us well at a client site, we also did not have to worry about them changing the main layout (yet). If that happens I am going to recommend migrating to the next approach which is the approach we are going to be implementing in this series of posts related to this topic. Build a Basepage base framework in a separate library and reference it in your websites – derive from this Base page and create 1-n Basepages that you can use within your site – make each Base page configurable in the Web.Config file
I mention ASP.NET 2.0 here because the fact that it’s coming soon will make most of the ASP.NET 1.1 techniques in this article obsolete. Comments – We have used this approach at several clients in the past, it has worked well in the past. One of the things I did not like about it was the lack of centralized configuration and no way to solidify the state objects in our site. It would be nice to have a framework that did all this, that is what we are going to do in this article. So why write this article? Simple, even though I have seen a lot of companies upgrade their systems to Windows 2003 Server, Windows XP and have embraced using the .NET framework, I do know of a few places that are still stuck using NT 4 Server (even though the client machines are on Windows 2000 Professional which for them are doing well). So yes, using this approach will be invalid when VS.NET 2005 is released to the world. But for the rest of us that get stuck working with old technology, this will be a huge timesaver. To close this section, I will describe the steps to get started on your own BasePage Framework implementation.
Add an XML file so we have a place to put our configuration stuff (this does not need to be part of the project, it’s just a nice place to keep our configuration schema for the Base pages). Add the following XML to the schema… <configSections>
<section name="base.configuration"
type="BasePageFramework.Configuration.BasePageSectionHandler,
BasePageFramework"/>
</configSections>
<base.configuration>
<page name="AcmeBase" defaultTitle="Acme Widgets">
<body>
<attribute name="topmargin" value="0"/>\
<attribute name="leftmargin" value="0"/>
<attribute name="rightmargin" value="0"/>
<attribute name="bottommargin" value="0"/>
<attribute name="bgcolor" value="#FFFF00"/>
</body>
<metatags>
<meta name="AcmeMeta" Content="dogs,cats,
fish,gerbils,pets,friends,mantees"/>
</metatags>
<links>
<css file="acmestyles.css" path="~/Content/styles"/>
<script file="acmescriptlib.js" path="~/Content/scripts"/>
</links>
</page>
<page name="AcmePrintFriendly" defaultTitle="Acme Widgets - Print">
<body>
<attribute name="topmargin" value="0"/>
<attribute name="leftmargin" value="0"/>
<attribute name="rightmargin" value="0"/>
<attribute name="bottommargin" value="0"/>
<attribute name="bgcolor" value="#FFFFFF"/>
</body>
<metatags>
<meta name="AcmeMeta" Content="dogs,cats,fish,
gerbils,pets,friends,mantees"/>
</metatags>
<links>
<css file="acmestyles.css" path="~/Content/styles"/>
<script file="acmescriptlib.js" path="~/Content/scripts"/>
</links>
</page>
</base.configuration>
Part II - Implementing the Configuration HandlerI am going to continue this where we left off in part one, so I am skipping the steps for setting up our Base Page Framework project. Refer to part one to see these steps… Now I like to centralize everything that is consistent about the pages in my site (i.e. script links, CSS files, body attributes, default name etc...) in one place. I have seen other base pages hard code this stuff which always makes me shudder. (As it should everyone because hard coding things like this should be avoided.) IMHO it makes sense to place these stuff in a location that can be changed without having to recompile our project. My favorite place for this is the Web.Config file. But since this is a separate framework assembly, it might be a good idea to not use the default The first thing we need to do for our base page framework is to create a custom configuration handler to parse the custom configuration settings in our web.config. (See part one for the schema). We do this by implementing the The public object Create(object parent, object configContext, XmlNode section);
Returns
Parameters
Let’s get started… I like to keep my items in any project logically separated, so create a folder for our configuration stuff. Call it “configuration”. Add a class to this folder and call it public class BasePageSectionHandler :
System.Configuration.IConfigurationSectionHandler
{
#region IConfigurationSectionHandler Members
public object Create(object parent, object configContext, XmlNode section)
{
System.Xml.XmlNodeList pages = section.SelectNodes("page");
return new BasePagesConfiguration(pages);
}
#endregion
}
In this implementation, we are going to return the next class we are going to create which is the public class BasePagesConfiguration : System.Collections.ReadOnlyCollectionBase
{
public BasePagesConfiguration(System.Xml.XmlNodeList pages)
{
foreach(System.Xml.XmlNode page in pages)
this.InnerList.Add(new BasePageConfiguration(page));
}
public BasePageConfiguration this[string pageName]
{
get
{
foreach(BasePageConfiguration pge in this.InnerList)
{
if(pge.Name == pageName)
return pge;
}
throw new System.InvalidOperationException("The base page ID "
+ pageName + " could not be found in" +
" the current configuration");
}
}
}
The indexer of this object returns a single Finally we get to the configuration object. We are going to aptly call this public class BasePageConfiguration
{
private string _pageName = string.Empty;
private NameValueCollection _bodyAttributes = new NameValueCollection();
private string _defaultTitle = string.Empty;
private NameValueCollection _scriptFiles = new NameValueCollection();
private NameValueCollection _cssFiles = new NameValueCollection();
private NameValueCollection _metaTags = new NameValueCollection();
public BasePageConfiguration(XmlNode xml)
{
//parse the xml passed to us and set the configuration state
//first get the name of this page
System.Xml.XmlAttribute pageName =
(XmlAttribute)xml.Attributes.GetNamedItem("name");
System.Xml.XmlAttribute defaultTitle =
(XmlAttribute)xml.Attributes.GetNamedItem("defaultTitle");
if(defaultTitle != null)
_defaultTitle = defaultTitle.Value;
else
_defaultTitle = string.Empty;
if(pageName != null)
_pageName = pageName.Value;
else
throw new System.Configuration.ConfigurationException("The name" +
" attribute is required on all basepage configuration entries.");
//get the body tag attributes
System.Xml.XmlNodeList bodyAttrib = xml.SelectNodes("body/attribute");
foreach(XmlNode attr in bodyAttrib)
{
XmlAttribute name =
(XmlAttribute)attr.Attributes.GetNamedItem("name");
XmlAttribute val =
(XmlAttribute)attr.Attributes.GetNamedItem("value");
if(name != null && val != null)
{
_bodyAttributes.Add(name.Value,val.Value);
}
}
//get the site settings
System.Xml.XmlNode title = xml.SelectSingleNode("site/title");
if(title != null)
{
XmlAttribute val =
(XmlAttribute)title.Attributes.GetNamedItem("value");
if(val != null)
_defaultTitle = val.Value;
}
//get the main page links
System.Xml.XmlNodeList cssFiles = xml.SelectNodes("links/css");
foreach(XmlNode css in cssFiles)
{
XmlAttribute file =
(XmlAttribute)css.Attributes.GetNamedItem("file");
XmlAttribute path =
(XmlAttribute)css.Attributes.GetNamedItem("path");
if(file != null && path != null)
_cssFiles.Add(file.Value,path.Value);
}
System.Xml.XmlNodeList scriptFiles = xml.SelectNodes("links/script");
foreach(XmlNode script in scriptFiles)
{
XmlAttribute file =
(XmlAttribute)script.Attributes.GetNamedItem("file");
XmlAttribute path =
(XmlAttribute)script.Attributes.GetNamedItem("path");
if(file != null && path != null)
_scriptFiles.Add(file.Value,path.Value);
}
// get the place holder settings so we can see what user
// controls to load into our base page placeholders
System.Xml.XmlNodeList metaTags = xml.SelectNodes("metatags/meta");
foreach(XmlNode tag in metaTags)
{
XmlAttribute name =
(XmlAttribute)tag.Attributes.GetNamedItem("name");
XmlAttribute content =
(XmlAttribute)tag.Attributes.GetNamedItem("Content");
this._metaTags.Add(name.Value,content.Value);
}
}
//do not allow outside access to the individual
//values of the configuration
public NameValueCollection BodyAttributes{get{return _bodyAttributes;}}
public HorizontalAlign SiteAlignment{get{return _alignment;}}
public string DefaultTitle{get{return _defaultTitle;}}
public NameValueCollection Scripts{get{return _scriptFiles;}}
public NameValueCollection StyleSheets{get{return _cssFiles;}}
public string Name{get{return _pageName;}}
public NameValueCollection MetaTags{get{return _metaTags;}}
}
Finally as you can see, the configuration is a read only object. This is logical because it will only get its values from the configuration file when the object is created by the configuration handler. Using this handler is easy. To create an instance of the configuration handler, you need to call Configuration.BasePagesConfiguration baseConfiguration =
(Configuration.BasePagesConfiguration)
System.Configuration.ConfigurationSettings.GetConfig(
"base.configuration");
Returns: the appropriate configuration object based on your section in the <configSections>
<section name="base.configuration"
type="BasePageFramework.Configuration.BasePageSectionHandler,
BasePageFramework"/>
</configSections>
The object that gets returned here is whatever you decided to return from your The name attribute of this configuration section is the configuration section in your .config file. The type is the fully qualified object name that implements the <section name="MyCustomSection"
type="System.Configuration.NameValueSectionHandler,system,
Version=1.0.3300.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089,custom=null" />
As you can see, you put the remaining four parts of the strong name into the type tag after your DLL name (separated by commas). In part 3, we are going to get started on the implementation of the Part III - Implementing the SuperBasePageI am going to continue this at the point where we left off creating the This part is a bit on the long side, I will try to keep it as short as possible given the amount of stuff that’s happening here. I will also post some UML class and sequence diagrams in the next part so there will be some decent docs to go with it. The public interface IBaseTemplate
{
PlaceHolder ContentArea
{get;}
}
public class SuperBasePage : WebUI.Page
{
private BaseServerForm _baseForm = null;
//new SuperBasePage("BASE_FORM");
//the form tag where we place all our webserver controls
private Configuration.BasePageConfiguration _config = null;
…
These three items when properly used will not only allow us to quickly create Base pages that can be configured from the web.config, but also allows us to leave the HTML in our individual pages on our site. I have seen other frameworks / Base page concepts where you need to remove the
To start with, we need to look at the existing objects at our disposal in the The There must be a better way, so instead of moving the content from our derived page to the Base page’s content area, we will do a little switcharoo with the
So in essence instead of our Base page high-jacking the content of each page, our Base page receives an public class BaseServerForm : HtmlUI.HtmlForm
{
private WebCtlUI.PlaceHolder _uiTemplatePlcHldr =
new WebCtlUI.PlaceHolder();
internal BaseServerForm(HtmlUI.HtmlForm oldFrm,IBaseTemplate uiTemplate)
{
foreach(string key in oldFrm.Attributes.Keys)
this.Attributes.Add(key,oldFrm.Attributes[key]);
System.Type frmTp = oldFrm.GetType();
// move the controls first - so they are located
// in the main form before we
// rip apart the htmlform via reflection
while(oldFrm.Controls.Count > 0)
uiTemplate.ContentArea.Controls.Add(
(System.Web.UI.Control)oldFrm.Controls[0]);
//copy the old form's values into our new form...
foreach(FieldInfo fields in
frmTp.GetFields(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance))
this.GetType().GetField(fields.Name,
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance).SetValue(this,
fields.GetValue(oldFrm));
this._uiTemplatePlcHldr.Controls.Add(
(System.Web.UI.Control)uiTemplate);
this.Controls.Add((System.Web.UI.Control)
this._uiTemplatePlcHldr);
}
public IBaseTemplate BaseTemplate
{
get{return (IBaseTemplate)_uiTemplatePlcHldr.Controls[0];}
}
}
The constructor of this class takes two things, an public class AcmeBaseTemplate : System.Web.UI.UserControl,
BasePageFramework.IBaseTemplate
{
protected System.Web.UI.WebControls.PlaceHolder _content;
protected System.Web.UI.WebControls.PlaceHolder _leftNav;
public PlaceHolder LeftNavigation
{get{return _leftNav;}}
#region IBaseTemplate Members
public PlaceHolder ContentArea
{
get
{
return _content;
}
}
#endregion
This template interface is how I get around a limitation in another framework that does almost the same thing this one does. Why define an interface to be used for our complete Base layout if we may want to change that layout in the future? What I think makes sense is to define a class that exposes our Base page’s content areas so we can change what they contain at will. Plus, it would also be nice to create different layouts with access to all areas in our page. This will also allow us to dynamically change our layouts at runtime under certain conditions. Finally, it would also be really nice if our designers could change the look of the Base page without going into the code but instead by using the designer. That is where using a UserControl (that implements our There is one gotcha however, we can’t be sure of what the template developer is going to call the area to place our content. We really need to know this so we can relocate the pages controls to this area in a nice seamless and discreet manner. This is where the You will see later on in the Base page definition why we need to expose the other placeholders in our user control to the outside world via properties as I have done in the above example. The Now that we have our public class SuperBasePage : WebUI.Page
{
#region Constants
//large string builder initial buffer siz
private const int LG_STR_BUFFER_SZ = 512;
//medium string builder inital buffer size
private const int MD_STR_BUFFER_SZ = 256;
//small (default) string builder buffer size
private const int SM_STR_BUFFER_SZ = 128;
private const string OPEN_TITLE_TAG = "";
private const string OPEN_HEAD_TAG = "";
private const string CLOSE_HEAD_TAG = "";
private const string OPEN_HTML_TAG = "";
private const int DEFAULT_LINK_WIDTH = 20;
//this is used to estimage the number of characters
//to reserve space for in our string builders
//in an attempt to maximize efficency
#endregion
#region SuperBasePage Members
//our form sits in this placeholder
private WebCtl.PlaceHolder _serverFormPlcHldr =
new WebCtl.PlaceHolder();
//the title of the page (displayed in the caption bar)
private string _title = string.Empty;
private BaseServerForm _baseForm = null;
//new SuperBasePage("BASE_FORM");
//the form tag where we place all our webserver controls
private Configuration.BasePageConfiguration _config = null;
private string _basePageName = string.Empty;
private ISmartQueryString _smartQueryString = null;
private ISmartSession _smartSession = null;
private ISmartApplication _smartApp = null;
#endregion
#region Properties
public string Title
{
get{return this._title;}
set{this._title = value;}
}
public BaseServerForm BaseForm
{get{return this._baseForm;}}
public IBaseTemplate MainUITemplate
{get{return this._baseForm.BaseTemplate;}}
public ISmartSession SmartSession
{
get{
if(this._smartSession == null)
throw new InvalidOperationException("You must" +
" initialize the smart session state" +
" before you can reference it");
else
return this._smartSession;
}
}
public ISmartApplication SmartApplication
{
get{
if(this._smartApp == null)
throw new InvalidOperationException("You must" +
" initialize the smart application state" +
" before you can reference it");
else
return this._smartApp;
}
}
public ISmartQueryString SmartQueryString
{
get
{
if(this._smartQueryString == null)
throw new InvalidOperationException("You must" +
" initialize the smart query string" +
" before you can reference it");
else
return this._smartQueryString;
}
}
#endregion
protected override void CreateChildControls()
{
InitSmartQueryString(ref this._smartQueryString);
if(this._smartQueryString != null)
this._smartQueryString.SetQueryStringInfo(this.Page);
InitSmartSession(ref this._smartSession);
if(this._smartSession != null)
this._smartSession.SetSessionState(this.Page.Session);
//allows us to set the base page state from
//the child page before any rendering of the base page occurs
BasePreRender(this._baseForm);
//call the virutal method to allow the base page
//to set the appropriate usercontrols into the base template
LoadTemplatePanels();
}
protected override void OnInit(EventArgs e)
{
this.EnsureChildControls();
base.OnInit (e);
}
protected override void AddParsedSubObject(Object obj)
{
//put all the configuration extras into our html header here
//make sure you modify the body tag as well since it is also
//editable from our custom configuration section
if(obj is HtmlCtl.HtmlForm)
{
BasePageFramework.IBaseTemplate tmplt = null;
this.LoadBaseUITemplate(ref tmplt);
if(tmplt == null)
throw new InvalidOperationException("Unable to" +
" load the base UI Template");
this._baseForm = new BaseServerForm((HtmlCtl.HtmlForm)obj,
tmplt);
//replace the object reference with
//our own "base server form"
obj = this._baseForm;
}
if(obj is Web.UI.LiteralControl)
{
Web.UI.LiteralControl htmlHeader = (Web.UI.LiteralControl)obj;
//we only need to do this processing when we are in the header
if(htmlHeader.Text.IndexOf(OPEN_HTML_TAG)>=0)
{
//we are going to need the stuff from
//the configuration now so load it up...
ConfigLoadingArgs cfgParam = new ConfigLoadingArgs();
InitializeConfiguration(ref cfgParam);
this._config = (Configuration.BasePageConfiguration)
Cache["BASE_CONFIGURATION_" + cfgParam.BasePageID];
if(this._config == null)
InitFromConfiguration(cfgParam.BasePageID);
if(this._config != null)
{
System.Text.StringBuilder htmlBuilder = new
System.Text.StringBuilder(htmlHeader.Text.Length*2);
htmlBuilder.Append(htmlHeader.Text);
//check to see if the current literal control
//being passed to us is actually
//the HTML header - if it is then we need
//to add any values from the custom web.config settings
//if the current page has any of the same
//values in the body tag or any inline scripts -
//then we need to allow the page to override
//the web.config settings -
//this is in following with the
//same pattern for everything in ASP.NET
//(machine.config-web.config-page...)
//see if the title is filled in if not
//insert the default title
//if the title tag is missing then add one now
int openingTitleTagOffset =
htmlHeader.Text.ToUpper().IndexOf(
OPEN_TITLE_TAG,0,htmlHeader.Text.Length);
int endTitleTagOffset =
htmlHeader.Text.ToUpper().IndexOf(
CLOSE_TITLE_TAG,0,htmlHeader.Text.Length);
endTitleTagOffset -= CLOSE_TITLE_TAG.Length-1;
int titleLen =
htmlHeader.Text.Substring(openingTitleTagOffset,
endTitleTagOffset -openingTitleTagOffset).Length;
//get the length of our title
if(openingTitleTagOffset >= 0)
{
//check the length of our title
//if the title is missing then add the default one
if(titleLen == 0)
htmlBuilder.Insert(openingTitleTagOffset+
OPEN_TITLE_TAG.Length,
this._config.DefaultTitle);
}
else
{
int openHeadTagOffset =
htmlHeader.Text.ToUpper().IndexOf(OPEN_HEAD_TAG,
0,htmlHeader.Text.Length);
//if the head tag is missing then add it
//otherwise just insert the default title if needed
//create another string Builder to handle
//our missing header/title tag
System.Text.StringBuilder subHtml =
new System.Text.StringBuilder((OPEN_HEAD_TAG.Length
+ CLOSE_HEAD_TAG.Length
+ OPEN_TITLE_TAG.Length
+ CLOSE_TITLE_TAG.Length)
+_config.DefaultTitle.Length);
if(openHeadTagOffset == -1)
{
subHtml.Append(OPEN_HEAD_TAG);
subHtml.Append(OPEN_TITLE_TAG);
subHtml.Append(_config.DefaultTitle);
subHtml.Append(CLOSE_TITLE_TAG);
subHtml.Append(CLOSE_HEAD_TAG);
//insert the output right
//after the opening HTML tag
htmlBuilder.Insert(OPEN_HTML_TAG.Length,
subHtml.ToString());
}
else
{
subHtml.Append(OPEN_TITLE_TAG);
subHtml.Append(_config.DefaultTitle);
subHtml.Append(CLOSE_TITLE_TAG);
//insert the output right after
//the opening Head Tag
htmlBuilder.Insert(openHeadTagOffset,
subHtml.ToString());
}
}
//insert our configuration metatags and
//scripts/links after the closing title tag...
//int closingTitleTagOffset =
htmlBuilder.ToString().ToUpper().IndexOf(CLOSE_HEAD_TAG,
0,htmlBuilder.ToString().Length);
htmlBuilder.Replace(CLOSE_HEAD_TAG,
GetWebConfigLinks(htmlBuilder.ToString()));
RenderBodyTagLinks(htmlBuilder);
//replace the text in the literal
//control with our new header
htmlHeader.Text = htmlBuilder.ToString();
}
}
}
this.Controls.Add((System.Web.UI.Control)obj);
}
#region HTML Header Parsing Code
private void RenderBodyTagLinks(System.Text.StringBuilder htmlBuilder)
{
//rip out the body tag and replace it with our own
//that has the attributes from the web config file
//but allows the individual pages to override
//these settings at the page level
string bodyTag = htmlBuilder.ToString();
int bodyTagOffset = bodyTag.ToUpper().IndexOf("<BODY");
bodyTag = bodyTag.Substring(bodyTagOffset,
bodyTag.Length - bodyTagOffset);
//create the attributes that will be dumped into the body tag
//make sure it is not already in the body before entering it
//if it is in the body then throw it out so the individual page
//can override the web.config's settings
System.Text.StringBuilder bodyTagBuilder =
new System.Text.StringBuilder(SM_STR_BUFFER_SZ);
bodyTagBuilder.Append("
foreach(string attribute in _config.BodyAttributes.Keys)
if(bodyTag.IndexOf(attribute) == -1)
{
bodyTagBuilder.Append(" ");
bodyTagBuilder.Append(attribute);
bodyTagBuilder.Append("=\"");
bodyTagBuilder.Append(
_config.BodyAttributes.Get(attribute));
bodyTagBuilder.Append("\" ");
}
//append the global body tag attributes to the
//end of the current attribute list in our body tag
string newAttribList = bodyTag.Substring(5,bodyTag.Length-6);
//remove the opening body tag and closing bracket
//get the current attributes out of the old body tag
//and add them to our bodytagbuilder
bodyTagBuilder.Append(" ");
bodyTagBuilder.Append(newAttribList);
int bodytagoffset = htmlBuilder.ToString().IndexOf(bodyTag);
//remove the old body tag from our string builder
htmlBuilder.Remove(bodytagoffset,htmlBuilder.Length-bodytagoffset);
string output = htmlBuilder.ToString();
htmlBuilder.Append(bodyTagBuilder.ToString());
}
//takes the existing script links and returns a new string
//containing the existing links merged with the ones in the web.config
private string GetWebConfigLinks(string oldHtml)
{
System.Text.StringBuilder subHtml =
new System.Text.StringBuilder((_config.Scripts.Count*
DEFAULT_LINK_WIDTH)+CLOSE_HEAD_TAG.Length);
this.GetMetaTags(subHtml,oldHtml);
this.GetStyleSheetLinks(subHtml);
this.GetScriptLinks(subHtml);
subHtml.Append("\r\n\t");
subHtml.Append(CLOSE_HEAD_TAG);
return subHtml.ToString();
}
private void GetMetaTags(System.Text.StringBuilder htmlBuilder,
string oldHtml)
{
foreach(string metaTag in _config.MetaTags.Keys)
{
if(oldHtml.IndexOf(metaTag) == -1)
{
htmlBuilder.Append("\r\n\t\t");
htmlBuilder.Append(" " +
" htmlBuilder.Append("NAME=\"");
htmlBuilder.Append(metaTag);
htmlBuilder.Append("\"");
htmlBuilder.Append(" CONTENT=\"");
htmlBuilder.Append(_config.MetaTags.Get(metaTag));
htmlBuilder.Append("\"/>");
}
}
}
private void GetStyleSheetLinks(System.Text.StringBuilder scriptLinks)
{
foreach(string key in this._config.StyleSheets.Keys)
{
scriptLinks.Append("\r\n\t\t " +
" scriptLinks.Append("type=\"text/css\" ");
scriptLinks.Append("rel=\"stylesheet\" ");
scriptLinks.Append("href=\"");
scriptLinks.Append(ParseRootPath(
this._config.StyleSheets.Get(key)));
scriptLinks.Append("/");
scriptLinks.Append(key);
scriptLinks.Append("\"");
scriptLinks.Append("/>");
scriptLinks.Append(Environment.NewLine);
}
}
private void GetScriptLinks(System.Text.StringBuilder scriptLinks)
{
foreach(string key in this._config.Scripts.Keys)
{
scriptLinks.Append("\r\n\t\t ");
}
}
#endregion
#region Utility Methods
private string ParseRootPath(string path)
{
if(path.IndexOf('~') == 0)
{
if(System.Web.HttpContext.Current.Request.ApplicationPath == "/")
{
path = path.Replace("~","");
}
else
path = path.Replace("~",
System.Web.HttpContext.Current.Request.ApplicationPath);
return path;
}
else
return path;
}
private void InitFromConfiguration(string pageName)
{
this._config =
(Configuration.BasePageConfiguration)Cache["BASE_CONFIGURATION_"
+ pageName];
if(this._config == null)
{
Configuration.BasePagesConfiguration baseConfiguration =
(Configuration.BasePagesConfiguration)
System.Configuration.ConfigurationSettings.GetConfig(
"base.configuration");
if(baseConfiguration == null)
return;
//the base configuration was not set
//in the current web.config - exit now...
//load the current page's configuration and stick it
//into cache so it does not have to be reloaded
//on subsequent requests
this._config = baseConfiguration[pageName];
Cache.Add("BASE_CONFIGURATION_" + pageName,
this._config,null,DateTime.MaxValue,
TimeSpan.FromDays(100),
System.Web.Caching.CacheItemPriority.NotRemovable,null);
}
}
#endregion
#region Virutal Methods
protected virtual void InitializeConfiguration(ref
ConfigLoadingArgs configParams)
{}
///
/// override this method in our child pages to allow us
/// to set base page state before any rendering takes place
/// in the base page
///
protected virtual void BasePreRender(BaseServerForm baseForm)
{}
///
/// override this method to allow the
/// base page to load the base UI template
/// the base UI template is of type BaseUITemplate
/// BaseUITemplate is of type Usercontrol
///
protected virtual void LoadBaseUITemplate(ref
BasePageFramework.IBaseTemplate tmplt)
{}
///
/// override in the base page so you can load
/// user controls at the derived base page
/// where you want them to be loaded - this is done
/// so the derived base page can subscribe to events
/// fired by these controls
///
protected virtual void LoadTemplatePanels()
{}
///
/// override this method to initialize the smart
/// querystring with your derived smart querystring instance
///
/// derived instance of a smart query string base
/// class that Implements the ISmartQueryString interface
protected virtual void InitSmartQueryString(ref
ISmartQueryString isqs)
{}
///
/// override this method to initialized the smart session
/// state with your derived smart session instance
///
/// derirved smart session object that implements
/// the ISmartSession Interface
protected virtual void InitSmartSession(ref ISmartSession ises)
{}
///
/// override this method to initialize the smart
/// application state with your derived smart application instance
///
/// derived smart application object that
/// implements the ISmartApplication interface
protected virtual void InitSmartApplication(ref
ISmartApplication iapp)
{}
#endregion
}
Here is how you implement a page that derives from the public class AcmeBase : BasePageFramework.SuperBasePage
{
protected override void LoadBaseUITemplate(ref
BasePageFramework.IBaseTemplate tmplt)
{
tmplt = (IBaseTemplate)
LoadControl("~/BaseTemplateControl.ascx");
}
protected override void InitializeConfiguration(ref
BasePageFramework.Configuration.ConfigLoadingArgs configParams)
{
configParams.BasePageID = "AcmeBase";
}
protected override void InitSmartApplication(ref
BasePageFramework.SmartState.ISmartApplication iapp)
{
iapp = (ISmartApplication)new AcmeAppObject();
}
protected override void InitSmartQueryString(ref
BasePageFramework.SmartState.ISmartQueryString isqs)
{
isqs = (ISmartQueryString)new AcmeSmartQueryString();
}
protected override void InitSmartSession(ref
BasePageFramework.SmartState.ISmartSession ises)
{
ises = (ISmartSession)new AcmeSmartSession();
}
}
And this is the derived base template implementation that gets loaded during the virtual public class AcmeBaseTemplateMain : System.Web.UI.UserControl,
BasePageFramework.IBaseTemplate
{
private PlaceHolder _header = new PlaceHolder();
private PlaceHolder _leftNavigation = new PlaceHolder();
private PlaceHolder _contentArea = new PlaceHolder();
private void Page_Load(object sender, System.EventArgs e)
{
// Put user code to initialize the page here
}
#region IBaseTemplate Members
public PlaceHolder ContentArea
{
get
{
return _contentArea;
//The IBaseTemplate Interface's ContentArea method
//is used by the SuperBasePage to handle
//content placement from the derived page
}
}
public PlaceHolder Header
{
get{return _header;}
//PLACEHOLDER THAT CONTAINS OUR HEADER'S CONTENT
}
public PlaceHolder LeftNav
{
get{return _leftNavigation;}
//PLACEHOLDER THAT CONTAINS OUR LEFT NAV BAR
}
#endregion
}
You can see that I am using a combination of overriding the For those of you unfamiliar with this method, it gets fired whenever a handler encounters a child element. In the case of the
As you may have already guessed, we only really care about the first two, when it is fired for the HTML tag, that is when we | ||||||||||||||||||||