SlideShare uma empresa Scribd logo
1 de 49
Building A Better ASP.NET 1.1 Basepage Framework - Part 1 -
Introduction

This 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 posts
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
    •    Pros

              o    No need to recreate the look and feel for each page

              o    You can see the look and feel in the designer (big deal) :P

    •    Cons

              o    You have to cut and paste the HTML into all your pages

              o    You have to maintain all the pages on the site to make the simplest
                   change to your site (not much of an advantage if you ask me)

Comments: This is the approach I have see 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
    •    Pros

              o    Simply drag and drop your UI into your new pages and your done!

    •    Cons

              o    Difficult to maintain the site because if you need to remove/add user
                   controls in the Base layout you have to touch every single page in the
                   site

              o    You cannot see the complete look and feel in the designer (again – big
                   deal)

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 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.
•    Pros

             o    Now were getting somewhere! We no longer have to maintain the
                  main look and feel across the entire site from each individual page –
                  nor do we need to worry about changes to what elements are added to
                  the main UI because we can change it all from one place

    •    Cons

             o    Very difficult to maintain from a designer perspective (there is no
                  designer support for this approach available) try telling that to your
                  web design team and see what happens.

             o    You will need to recompile the site if the slightest HTML changes need
                  to be made to the look and feel of the site.

             o    You should not do this in the OnInit() because it does not guarantee
                  that your controls will be in place at all phases of execution within your
                  ASP.NET pipeline, the code for generating the base page should be in
                  the CreateChildControls() call. You can force CreateChildControls() to
                  be called by calling EnsureChildControls().

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.
    •    Pros

             o    Designers can alter the HTML of each user control without recompiling
                  the Website DLL (unless they add server controls).

             o    You can make changes to the site’s look and feel without having to
                  change any Base page code (in most cases).

    •    Cons

             o    You still have to deal with hardcoding the layout of your controls into
                  the Base page (not cool).

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 the 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

    •    Pros
o    Designers can alter the layout and look and feel of your site from not
                   only the separate UserControls (which I call base controls or Base
                   controls) but also from the UI layout, which is just another
                   UserControls with placeholders
              o    All information about how to generate the HTML header and what
                   styles / scripts to link in and how to set up the body tag can be altered
                   from the web.config
    •    Cons
         Still – the full support of having the Base page elements viewable in the
         designer is not available (you’ll just have to wait for the MasterPages in
         ASP.NET 2.0

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 these posts.

So why write these posts? 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 still 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 I 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 post I will describe the steps to get started on your own BasePage
Framework implementation.

1: Create a Class Library Project call it “BasePageFramework”.
2: Add the System.Web reference.
3: Create a SuperBasePage class and inherit from System.Web.UI.Page.
4: Create a class and call it BasePageConfigurationHandler. Implement the
System.Configuration.ICustomConfigurationHandler interface.

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 Basepages).

Add the following XML to the Schema…

<configSections>
       <section name="base.configuration"
               type="BasePageFramework.Configuration.BasePageSectionHan
dler,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>



Building A Better ASP.NET 1.1 Basepage Framework - Part Two -
Implementing the Configuration Handler
I 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 basepages hard code this stuff which always makes me shudder. (As it should
everyone because hardcoding things like this should be avoided).

IMHO it makes sence to place this 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 <add key=““ value=““/> settings available in the <appsettings> section
because I personally think this is less intuitive as to what the settings are for. Plus it
would be nice to not clutter our appsettings for this application with configuration
information that is going to be used across 1 to many sites. To solve this problem
we create a custom configuration section for our Base Page Framework. That is the
subject of this post.
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
IConfigurationSectionHandler interface which is part of the System.Configuration
namespace. This is really easy to do and if you know how to parse XML you are
4/5th of the way there.

The IConfigurationSectionHandler interface has one method called Create() here is
the prototype for Create and what each argument is intended to provide.

public object Create(object parent, object configContext, XmlNode
section)

Returns object : this object can be anything you want I like to create a separate
configuration class that holds all my settings and return it so I can cache it for later
use. This example will be doing the very same thing.

object parent

The parent config section.

object configContext

An HttpConfigurationContext object. This is only passed to us when we call it from
ASP.NET, as you probably guessed if you do this out of a windows app via an
app.config file this parameter will be null.

XmlNode section

This is our custom configuration section. We will be parsing this to get our
configuration settings out of the config file.

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
BasePageConfigurationSectionHandler. This class will be our configuration handler
so implement the IConfigurationSectionHandler interface. Here is the code.

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 BasePagesConfiguration class. If you look at the schema for our
configuration you will see that there can be 1:n page elements. This is because you
can have more than one base page in your site. So you if you guessed that
BasePagesConfiguration is a collectionbase object then you guessed right. We are
going to need to implement this object next. Here is the code.

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 BasePageConfiguration object via a key
value. I am not too concerned with how quickly the collection returns individual
configuration objects so I am using a simple loop. The reason we don’t really care
about performance here is because our base page will internally cache it’s
configuration object so it does not have to keep going out to the config file everytime
it gets requested.

Finally we get to the configuration object we are going to aptly call this
BasePageConfiguration. Create the BasePageConfiguration class in your project here
is the code. I am going to assume that you know how to parse XML documents so I
am not going to go into detail on how that works. I will just explain what each field
of this object is for.

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;}}
}
This is the ID of our base page. Each base page in the system must have a unique
name so it’s configuration can be located in the config file (more on this later when
we get into implementing the actual basepagebase).

private NameValueCollection _bodyAttributes

These are the individual body attribute tags that will go into each and every one of
our pages. The are injected into the HTML in the basepagebase
OnAddParsedSubObject() call (many thanks to Michael Earls for showing me the light
on this one).

private string _defaultTitle

This is the default title that will be used on all pages of our site. Should the HTML
designer not put anything into the <Title> tag we will replace that blank value with
this one.

private NameValueCollection _scriptFiles

This is a list of script links that will be placed inside the <head> tags.

private NameValueCollection _cssFiles

This is a list of Stylesheet links that will be placed inside the <head> tags.

private NameValueCollection _metaTags

This is a list of default metatags that will appear in all pages of our site.

Finally as you can see the configuration is a read only object. This is logical because
it will only get it’s 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 GetConfig() in your ConfigurationSettings object.

Configuration.BasePagesConfiguration baseConfiguration
=(Configuration.BasePagesConfiguration)System.Configuration.Configurati
onSettings.GetConfig("base.configuration");

Returns: the appropriate configuration object based on your section in the
configSections tag. See the following snippet taken from the schema we created in
part one.

<configSections>
      <section name="base.configuration"

type="BasePageFramework.Configuration.BasePageSectionHandler,BasePageFr
amework"/>
</configSections>

The object that gets returned here is whatever you decided to return from your
IConfigurationSectionHandler.Create implementation in this case it is our
ReadOnlyCollectionBase “BasePagesConfiguration“ object that contains the individual
BasePageConfiguration objects for our site.
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
IConfiguraitonSectionHandler interface along with the name of the assembly this
object resides in separated by a comma. If you are referencing a strong named
assembly then you need to put all four parts of the assembly name into the type
attribute like this (taken from System.dll) …

<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 SuperBasePage
itself. To close in part 4 I am going to show you how to throw in some extra
features that wrap standard site state management the objects are classes that
implement the ISmartQueryString, ISmartSession, and ISmartApplication interfaces.


Building A Better ASP.NET 1.1 Basepage Framework - Part Three -
Implementing the SuperBasePage
I am going to continue this at the point where we left off creating the
BasePageConfiguration class. In this post are going to implement the
SuperBasePage that will be used in all derived Base pages of your site. It is useful to
have multiple Base pages in your site because you might want to have a layout for
printer friendly pages, popup windows, dialogs, etc…

This one 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 post so there will be some decent docs to go with it.

The SuperBasePage is actually two separate classes and a user control that
implements a custom interface all working together to form a template. They are
declared as follows.

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
Basepages 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 /
Basepage concepts where you need to remove the HTML, HEAD, TITLE, BODY, and
FORM tags in order to be useable. We will not be doing that here. It is important
that we keep that part intact for two reasons, 1: Designers can understand it better
so you do not need to worry about whether or not they will replace these tags in
error and more importantly 2: Keeping these tags in each page allows us to override
what is placed in the web.config (like trace directives etc… our framework follows the
same pattern).

To start with we need to look at the existing objects at our disposal in the
System.Web Namespace, the ones we are interested in right now are
System.Web.UI.Page and System.Web.UI.HtmlControls.HtmlForm. The later of the
two is what we will be worrying about first.

The HtmlForm when set with a server tag becomes the heart of the Ubiquitous
ASP.NET webform. You cannot instantiate server controls on your page without it.
Herein lies our problem, how can we get our form which we have to define on each
page to behave the same by displaying the same outlying content with unique
content for each page, and more importantly, how do we get the content from our
page to actually nest itself within this form’s content area. What I have seen done in
past Base pages I have developed is to “move” the content from each of their pages
to a specified “content area” in their Base page class. We are still doing that here
but in a more discreet way. And, as I have already mentioned, most of the
examples force you to delete the HTML header and footer along with the Form tag
from your page before using it (try explaining that to a web-designer and see what
they do).

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 HtmlForm and
change it into a form that has the following functionality…

1: Has a reference to a template that defines the layout for all our pages

2: This form needs to know how to contruct it in such a way that when you pass it an
existing form, it can take the place of the old form with one important difference – It
will have functionality for placing our individual pages content where the it all
belongs.

So in essence instead of our BasePage highjacking the content of each page, our
Base page receives an htmlform and makes it work in such a way that allows us to
set content wherever we want from a given template that the form will initialize
when it gets created. You will notice that our BaseServerForm is nothing more than a
plain old HtmlForm with a template reference. The following is the code for our Base
server form…

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.Contr
ols[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.Insta
nce).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 HtmlForm object and a reference to
something called an IBaseTemplate. This is how you will implement the
IBaseTemplate in your code…

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 BasePage’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 BasePage without
going into the code but instead by using the designer. That is where using a
UserControl (that implements our IBaseTemplate interface) Comes in handy. 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 IBaseTemplate interface comes in to play. As you can see in the above example,
we define a placeholder where our derived pages content will go in the Base page
and return it through this interfaces only property “ContentArea” (which
coincidentally returns a placeholder for our content). Now our BasePage will have a
contract with the UserControl that says “I will assume responsibility of locating the
controls where they belong so you don’t have too”.

You will see later on in the Base page definition why we need to expose the other
placeholders in our usercontrol to the outside world via properties as I have done in
the above example.

The BaseServerForm class doesn’t do much other than encapsulate the movement of
our pages content to the template’s content area (and perform the role of server
form) so beside loading a UI template, it is basically just a server form with a layout
defined. I should also note that we do not want anything but the Basepage creating
a Baseserverform, so we hide the constructor from the outside world by declaring it
internal. This is because the BasePage is the only object that should ever need to
crate a BaseServerForm. However, we do want developers to be able to reference
the form in the case they decide to change something programmatically before the
request is handled. I had to resort to using reflection to rip apart the old HtmlForm’s
private values and copy them to our BaseServerForm, we could of used a “has-a”
relationship with the HtmlForm but that seemed a bit on the clunky side, therefore I
made it an is-a relationship to make it’s relationship with our pages seamless. Also
not that it is critical that we move the control tree and attributes of the old htmlform
to our new BaseServerForm before we do use reflection to get any internal values,
we can get to the control tree just fine, we don’t need to use reflection to get to it. I
normally do not like using reflection unless absolutely required, I like to think of
reflection like I think of Duct Tape, it’s a tool that has a noble purpose… But when
used “creatively” you can end up really shooting yourself in the foot (if you will
forgive the old C cliché).
Now that we have our BaseServerForm class defined we need to define the
superbasepage from which we will derive all our sites base pages. Here is the code…

public class SuperBasePage : WebUI.Page
{

      #region Constants
      private const int LG_STR_BUFFER_SZ = 512; //large string builder
initial buffer siz
      private const int MD_STR_BUFFER_SZ = 256; //medium string
builder inital buffer size
      private const int SM_STR_BUFFER_SZ = 128; //small (default)
string builder buffer size
      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

      private WebCtl.PlaceHolder _serverFormPlcHldr = new
WebCtl.PlaceHolder(); //our form sits in this placeholder
      private string _title = string.Empty; //the title of the page
(displayed in the caption bar)
      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);


            BasePreRender(this._baseForm);     //allows us to set the
base page state from the child page before any rendering of the base
page occurs
            LoadTemplatePanels(); //call the virutal method to allow
the base page to set the appropriate usercontrols into the base template

      }

      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);
               obj = this._baseForm; //replace the object reference
with our own "base server form"
           }

            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.Leng
th);
                              int endTitleTagOffset =
htmlHeader.Text.ToUpper().IndexOf(CLOSE_TITLE_TAG,0,htmlHeader.Text.Len
gth);
                              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._co
nfig.DefaultTitle);
                              }
                              else
                              {
                                   int openHeadTagOffset =
htmlHeader.Text.ToUpper().IndexOf(OPEN_HEAD_TAG,0,htmlHeader.Text.Lengt
h);
                                    //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.DefaultT
itle);
                                        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.T
oString().Length);


htmlBuilder.Replace(CLOSE_HEAD_TAG,GetWebConfigLinks(htmlBuilder.ToStri
ng()));

                              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");<
SPAN>

            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("rnt");

           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("rntt");

                        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("rntt
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("rntt     ");

           }

     }



     #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.Configurat
           ion.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),Sys
tem.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 SuperBasePage



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 LoadBaseUITemplate call

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 AddParsedSubObject()
and the AddChildControls() virtual methods. This is because we only need to use the
AddParsedSubObject() method to do one thing. Intercept our html header and html
form before it gets added to our page. I got this Idea from a Micheal Earls (a fellow
Magenicon from Atlanta) check out his blog here http://www.cerkit.com. I have
seen this method overriden in server controls for child control tags but not in a page
class like this. I thought it was very clever use of the method and it solves the
problem of having to remove the outer html from around our content area. Now that
we have the outer HTML intact it also gives us the opertunity to override the Base
page configuration at the page level thus imitating the web.config settings/page
directive pattern that currently exists in ASP.NET.

For those of you unfamiliar with this method. It gets fired whenever a handler
encounterers a child element. In the case of the System.Web.Page class it gets fired
a total of three times.



      1 Once when it encounters the tag

      2 Once when it encounters the tag

      3 And one more time when it encounters the tag

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 get our custom configuration settings and
place them into the header. This part of the handler needs to really scream so I
struggled for awhile on what would be the best way, I have used HtmlWriters in the
past but I thought for this it was too much overhead since we are already using one
in the render call, plus I am forced to parse all the HTML in order to make that
approach effective (so clearly it is not an option here). After some experimentation I
decided to go with StringBuilders since they are faster than vanilla string
concatenation (in fact the performance of the StringBuilder is comparable to using
the old memcpy() call in C). To truly take advantage of the StringBuilder’s
performance you need to estimate how much space you will need ahead of time, if
you don’t the StringBuilder will be moving a lot of character memory around when it
has to resize itself internally to make more room I use a combination of the default
HTML content along with what is in the web.config to calculate a good initial size for
my StringBuilder’s buffer. The reason this is so important is because when the
stringbuilder reaches its limit during an append(), it creates a new buffer twice the
size of the old one and copies all the character data over to the new buffer (then
destroys the old buffer) all this movement of character data negates the
performance gains of using a stringbuilder (although it is still probably faster than
plain old string concatenation operators). Regardless, it’s better to be safe than
sorry IMHO.

We are doing some initialization in a the CreateChildControls override as well. We do
this to ensure that these methods are only called once when the request gets
handled. This is done by calling EnsureChildControls() from our init() call. This call
“ensures” that our child controls get created (in this case we are initializing the
SmartQueryString and SmartSessionState) Since they are not critical to using the
Base page and since I am trying to keep this short, I will go into more detail on
these two classes in the final post.

The virtual methods in the SuperBasePage are to be overridden in our derived
SuperBasePage class. Using these virtual methods allows us to define how and what
our Base page loads when it is requested.

Here is what each method does…

protected virtual void InitializeConfiguration(ref ConfigLoadingArgs
configParams)
Override this method so we can tell the BasePage what page configuration to use
(remember that we have more than one page element in our config section? This is
where we specify what section to use). In this example the BasePage name attribute
we want from our configuration is “AcmeBase”. We are passing the configParams
argument by reference so we can set it in our override (in the derived base page).

Here is an example of how to implement it

protected override void InitializeConfiguration(ref
BasePageFramework.Configuration.ConfigLoadingArgs configParams)

{

       configParams.BasePageID = "AcmeBase";

}



This in turn will tell our Super Base page to load settings from the following page
config section…

<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>

The argument is passed by reference so we can set the BasePage name from our
derived Base page class please note that if the configuration element does not exist I
will throw you an exception (as you can see in the SuperBasePage code). You can
also see that I am caching the settings (I do this for two reasons) 1: If I want to use
the same config for more than one page – the configuration will only be created in
memory once and shared with all pages that use it. 2: We do not want to parse the
configuration on every request as this will cause a serious performance hit.

Here is the ConfigLoadingArgs class that is getting passed to our derived page from
the SuperBasePage through this virtual method.

public class ConfigLoadingArgs

{

       private string _basePageID = string.Empty;



       internal ConfigLoadingArgs()

       {}



       public string BasePageID

       {

              get{return _basePageID;}

              set{_basePageID = value;}

       }

}




protected virtual void BasePreRender(BaseServerForm baseForm)

This method gets called in our create child controls call before we load any templates
in to our Base UI. I have never needed to use this yet myself but I thought it may
be necessary for someone to access the BaseServerForm for this page before it loads
any controls. This is where you will do it.

protected virtual void LoadBaseUITemplate(ref
BasePageFramework.IBaseTemplate tmplt)

This is where we pull in our main user control (that implements the IBaseTemplate
interface) that will provide us with a layout for all the pages in our site. Internally
when the BaseServerForm is loaded and initialized – it will use the IBaseTemplate
interface to move the content to the appropriate area.

Here is how I use it in my derived Base page class – the beauty here is that we are
not required to load every single control into all the areas of the main template, you
can have some static and some dynamically loaded (in the load template panels call
you will see how we dynamically load controls which we usually do when we want
our derived base page to subscribe to a given controls events.

protected override void LoadBaseUITemplate(ref
BasePageFramework.IBaseTemplate tmplt)

{

       tmplt = (IBaseTemplate)LoadControl("~/BaseTemplateControl.ascx");

}




protected virtual void LoadTemplatePanels()



This override allows us to dynamically load controls into the areas of our Base
template after the Base template UI and form are loaded and initialized. The reason
you may need to do this is so you can attach events and subscribe to them in your
derived Basepage class (like navigation postbacks, logout requests, etc…).
Remember I told you earlier that I was going to let you in as to why I was exposing
all the placeholders in my BaseTemplate user control? You can see why in the
following snippet of code. I can get to those place holders in my derived Base page.
There is a catch however, you need to downcast the initialized IBaseTemplate to
whatever class your MainTemplate user control type is within this override before
you can get to the placeholder properties.



Here is how I use it (I am including an Event example so you can see what I want
my Base page to do when the event gets fired)…

protected override void LoadTemplatePanels()

{


this.BaseForm.BaseTemplate.Header.Controls.Add(LoadControl("~/BaseContr
ols/AcmeHeader.ascx"));

       _leftNavCtl = (LeftNav)LoadControl("~/BaseControls/LeftNav.ascx");

       this.BaseForm.BaseTemplate.LeftNav.Controls.Add(_leftNavCtl );

       _leftNavCtl.OnNavEvent += new NavEventHandler(OnNavigationEvent);

}



private void OnNavigationEvent(object sender,NavEventArg e)
{

       this.SmartQueryString.SmartRedirect(e.NavURL);

}

These are just examples I plan to release a full project with examples on how to do
everything at a future date.

These last two involve the SmartQueryString and SmartSession interfaces. The
smart handler interfaces give us the ability to strongly type our querystring, session
and application objects. I will go into more detail on the next post on what these
methods are used for…

protected virtual void InitSmartQueryString(ref ISmartQueryString isqs)

protected virtual void InitSmartSession(ref ISmartSession ises)

protected virtual void InitSmartApplication(ref ISmartApplication iapp)



Other methods in the Base page base…
private string ParseRootPath(string path)

This method ensures that we always know where in the site our
controls/resources/etc… are located in relation to the derived page. It uses the
standard ASP.NET recognition of the tilde “~” as the delimiter for our application
root. This is important because we may be running our application out of a virtual
directory (in the case of running it from the root of a website we could have just
used the standard forward slash “/” in front of our links to specify the site’s root).
Don’t forget that this page can be used by all pages of your site in any subdirectory,
which also means that since your pages are in different directories / subdirectories,
you need this so your pages can always find the user controls and other site
resources, etc...).

private void InitFromConfiguration(string pageName)

This is where we get our custom configuration settings. Remember that one of the
features of the framework is that the individual pages can override the settings you
put in the web.config. That way if you want the same layout (but a different
backcolor,script,metatag,etc…) you can do it without having to hard code it, just do
it the way any web designer would do it, put it in the hypertext where it belongs. If
you don’t override it, then the page will use the settings from the web.config. This
follows the same “machine/web/page” pattern used in all the directives of ASP.NET.

Well, that was a lot to say (and for you to read) I tried to keep it short since this is
just a blog post, so if you have any questions just ask and I will be happy to
answer. In the last post of this series I will talk about the Smart Handler objects
(SmartSession, SmartQueryString, SmartApplication) and how you can create your
own base handlers to persist values differently. To explain what they are for, they
allow us to manage our querystring, etc… from a within our Base page (like the
response and request objects already do – these are strongly typed and easy to
use). They have made my life infinitely easier.
Building A Better ASP.NET 1.1 Basepage Framework - Part Four -
Implementing the Smart Session Objects
In this final post (for now anyway until someone gives me some better ideas or I
wake up in the middle of the night with one of my own) I am going to describe how
to implement the Smart State Objects that we can get data from via the master
page. One of the things I really don’t like to do is having to hardcode strings to find
stuff in a generic collection object and then rely on that object being of a specific
type before I can actually use it. When I am working on the project independently it
is not as much of a problem, but when it grows to more than a couple of developers
it gets tough to keep track of the keys (unless of course you step through the entire
application with trace turned on so you can monitor additions and removals from our
state objects.

This gets even worse when dealing with querystring data. You can add and remove
things from the query string anywhere and it does not have to be consistent so when
you redirect to another page on the web app, you are assuming that the next page is
always going to have access to the query string elements you are going to need and
worse, that the values that they represent are not going to change in context. My
solution to this is to wrap these objects in my own smart session classes. We had a
monster problem synching up the query string that we were using at an Ecommerce
project once, and the minute the scope shifted and the client stated that they
wanted another feature (and another querystring because this site had to do all it’s
state maintenance through the query string) we had to go through every page in our
purchasing and payment process, etc… to make sure this element was added when it
was needed (and worse – they were using string concatenation operators to do it on
every single redirect in the site). Yuck!!!

So I took a little time and wisely spent some time coming up with a better way to
handle this problem, the solution is the ISmartQueryString, ISmartSession and
ISmartApplication interfaces. Here is an example of the Interfaces followed by what
they allow us to do…

public interface ISmartSession

{

        void SetSessionState(System.Web.SessionState.HttpSessionState
ses);

        void FlushSessionState();

        void AbandonSession();

        void PersistToSession();

}



public interface ISmartQueryString

{
void SetQueryStringInfo(System.Web.UI.Page webPage);

       void SmartRedirect(string url);

}



public interface ISmartApplication

{

       void FlushAppState();

       void PersistToAppState();

       void SetApplicationState(System.Web.HttpApplicationState state);

}

ISmartQueryStirng: Allows us to encapsulate the query string in one spot in our
code – we are always going to be assured of an elements exsistence (with default
values set) so we do not need to write the code to check this in every single page.
We will implement this interface in our SmartQueryStringBase class (more on that in
a bit).

Here are the methods that need to be implemented in the ISmartQueryString

void SetQueryStringInfo(System.Web.UI.Page webPage);

This is where we pass our http handler (the page object in the case of ASP.NET) to
our smart query string base. We need to pass the whole thing because we are going
to be using several items out of not only the request object but also the response
object as you will see in a bit.

void SmartRedirect(string url);

This is the magic call that keeps our querystring alive in the system. Yes your
developers can go ahead and just use the Response call directly and break it but that
is easily remedied if you find it somewhere in the code (try using a Find on
Response. And you will find it in no time!) Another thing you could do is use FXCop
to keep it from happening if you need to automate the restriction. Then you can do
whatever you do to the misguided developer and keep in on focus with the rest of
the team.

Now a framework wouldn’t be complete with an out of the box implementation of this
interface so I am providing one. In this interface I am using reflection to get the
values out of my SmartQueryString object (I am going to call it
AcmeSmartQueryString() for lack of a better name). You can see in the following
code how reflection takes the property values of our smart querystring and moves
the value to our querystring. I am also putting a prefix on this “sqs” for “Smart
Query String”. This tells our smart query string class that there is a query string
present and it can begin parsing it out back into our AcmeSmartQueryString() object
when the MasterPage is initialized during a request. Here is the code…

[Serializable()]
public abstract class SmartQueryStringBase : ISmartQueryString

{

      [field: NonSerialized()]

      private System.Web.UI.Page _page = null;



      public void SetQueryStringInfo(System.Web.UI.Page webPage)

      {

            _page = webPage;

            ExtractQueryStringInfo();

      }



      public void SmartRedirect(string url)

      {

            //build a querystring from the internal elements of this
class and redirect the user

            string qs = GenerateQueryString();




            if(qs != string.Empty)

                    _page.Response.Redirect(ParseRootPath(url) + "?
sqs=true&" + qs);

            else

                    _page.Response.Redirect(ParseRootPath(url));

      }



      //use reflection to take our objects field values and format it
into a querystring

      private string GenerateQueryString()

      {

            PropertyInfo [] props = this.GetType().GetProperties();

            StringBuilder sb = new StringBuilder();
foreach(PropertyInfo prop in props)

           {


BasePageFramework.Attributes.SmartQueryStringKeyAttribute [] attb   =


(BasePageFramework.Attributes.SmartQueryStringKeyAttribute [])


prop.GetCustomAttributes(typeof(BasePageFramework.Attributes.SmartQuery
StringKeyAttribute),

                 true);



                 //use the name if the attribute was omitted from the
property

                 if(attb.Length == 0)

                        sb.Append(prop.Name + "=" +
prop.GetValue(this,null).ToString() + "&");

                 else

                        sb.Append(attb[0].KeyName + "=" +
prop.GetValue(this,null).ToString() + "&");

           }



           return sb.ToString();

      }



      public override string ToString()

      {

           return "?sqs=true&" + GenerateQueryString();

      }



      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 ExtractQueryStringInfo()

     {

           string sqsElement = _page.Request.QueryString["sqs"];

           if(sqsElement != null && sqsElement != string.Empty)

           {

                  PropertyInfo [] props =
this.GetType().GetProperties();



                  foreach(PropertyInfo prop in props)

                  {


BasePageFramework.Attributes.SmartQueryStringKeyAttribute [] attb =
(BasePageFramework.Attributes.SmartQueryStringKeyAttribute
[])prop.GetCustomAttributes(typeof(BasePageFramework.Attributes.SmartQu
eryStringKeyAttribute),true);
if(attb.Length == 0)


prop.SetValue(this,GetFieldValue(prop.Name),null);

                            else


prop.SetValue(this,GetFieldValue(attb[0].KeyName),null);

                     }

              }

       }



       private object GetFieldValue(string elementName)

       {

              string retval = _page.Request.QueryString.Get(elementName);

              if(retval != null)

                     return retval;

              else

                     return string.Empty;

       }

}

Finally, if you look you can see what the SmartRedirect does. It’s nice because you
only need supply the smart redirect with your URL where you are going and the
smart query string does the rest of the work for you! Not too shabby!

Now if you are looking closely you will also see that I am using an attribute to
generate the key name for our query string. This is the
SmartQueryStringKeyAttribute(). We will apply this attribute to our
AcmeSmartQueryString object’s properties so we can keep the key values short and
sweet in our query strings output.

And here is the attribute class we are using to create our key value output…

public class SmartQueryStringKeyAttribute : System.Attribute

{

       private string _queryStringKey = string.Empty;
public SmartQueryStringKeyAttribute(string queryStringKey)

      {

             _queryStringKey = queryStringKey;

      }



      public string KeyName

      {

             get{return _queryStringKey;}

      }

}

Finally – here is the AcmeSmartQueryString class (our derived smart query string
object that holds our smart query strings values for the app).

public class AcmeSmartQueryString :
BasePageFramework.SmartState.SmartQueryStringBase
{



      private string _pgNum = string.Empty;

      private string _tabIdx = string.Empty;

      private string _userName = string.Empty;



      public AcmeSmartQueryString()

      {}



      [BasePageFramework.Attributes.SmartQueryStringKey("pn")]

      public string PageNumber

      {

             get{return _pgNum;}

             set{_pgNum = value;}

      }

      [BasePageFramework.Attributes.SmartQueryStringKey("ti")]

      public string TabIndex
{

              get{return _tabIdx;}

              set{_tabIdx = value;}

       }

       [BasePageFramework.Attributes.SmartQueryStringKey("un")]

       public string UserName

       {

              get{return _userName;}

              set{_userName = value;}

       }




}

You can see that this class does not interact with it’s base members, this is the
beauty of using the interface. Your do not need to worry about what is happening to
translate the query string to and from this object because it all happens behind the
scenes. You are now free to interact with this object as you need just like it’s
another instance in your code. I left out one of the attributes so you can see what
happens on the receiving end when the next page request is filled. You will see the
whole name for the property. It just has state and that’s it! That’s the beauty of it,
our developers using this framework do not need to know what is happening under
the hood, they just have to interact with this instance and they are good to go…
Keep in mind that the smart query string although maps properties to our smart
query string, you can only use strings with it (hence the name) I am looking into
ways to allow you to add other types to the query string (Enumerations, other value
types etc…). I will leave that matter as the subject of a future post.

Here is our Acme Master Page that not only initializes the other Master Page
components but also loads our AcmeSmartQueryString

public class AcmeBasePage : BasePageFramework.SuperBasePage

{



       private AcmeControls.LeftNav _leftNav = null;



      protected override void LoadBaseUITemplate(ref
BasePageFramework.IBaseTemplate tmplt)
{

            tmplt =
(BasePageFramework.IBaseTemplate)LoadControl("~/Base/AcmeBaseTemplate.a
scx");

     }



     protected override void LoadTemplatePanels()

     {

            _leftNav =
(AcmeControls.LeftNav)LoadControl("~/AcmeControls/LeftNav.ascx");


((AcmeBaseTemplate)this.MainUITemplate).LeftNavigation.Controls.Add(_le
ftNav);

            _leftNav.OnNavClick += new
AcmeWidgets.AcmeControls.LeftNavEvent(_leftNav_OnNavClick);



     }



      protected override void InitializeConfiguration(ref
BasePageFramework.Configuration.ConfigLoadingArgs configParams)

     {

           configParams.BasePageID = "AcmeBase";

     }

      protected override void InitSmartQueryString(ref
BasePageFramework.SmartState.ISmartQueryString isqs)

     {

            isqs = (BasePageFramework.SmartState.ISmartQueryString)new
AcmeSmartState.AcmeSmartQueryString();

     }

      protected override void InitSmartSession(ref
BasePageFramework.SmartState.ISmartSession ises)

     {

            ises = (BasePageFramework.SmartState.ISmartSession)new
AcmeSmartState.AcmeSmartSession();

     }
private void _leftNav_OnNavClick(object sender,
AcmeWidgets.AcmeControls.LeftNavEventArg e)

       {

              switch(e.PageNumber)

              {

                     case "1":


this.SmartQueryString.SmartRedirect("Default.aspx");

                            break;

                     case "2":


this.SmartQueryString.SmartRedirect("PageTwo.aspx");

                            break;

                     case "3":


this.SmartQueryString.SmartRedirect("PageThree.aspx");

                            break;

                     case "4":


this.SmartQueryString.SmartRedirect("PageFour.aspx");

                            break;

                     default:


this.SmartQueryString.SmartRedirect("Default.aspx");

                            break;

              }

       }

}

You can see in our event handler in the above page that we are using the
SmartQueryString of the current instance (initialized in our base page when it gets
requested) to do the redirection and keep our string elements intact, this not only
makes it easier to manage the querystring, it also centralizes everything that
happens in the querystring in one place making maintenance infinitely easier.
On a side note, So I know what some of you are saying… But I HATE using
reflection!! Why not use a formatter or typeconverter instead??!! To answer that
question, you can write your own. I created interfaces for these objects so you can
create your own smartquerystringbase class (along with the other smart state
objects) – the master page base does not care – all it needs to keep a reference to
are these interfaces so write your own if you must.

Now for the other smart state objects things are not as automatic because we do not
have the “Smart Redirect” to pass the data along to our code so it can be persisted.
Therefore we need o tell the other SmartState Objects to persist themselves
whenever we change something in them. These interfaces are fairly straight forward
minus the fact that we have to set the state somehow, that is where
“SetApplicationState” and “SetSessionState” come in. Pass these calls the
appropriate state object and away you go!

Here is our SmartSessionBase class that comes out of the box.

[Serializable]

public class SmartSessionBase : ISmartSession

{

       // Fields

       [field: NonSerialized()]

       private HttpSessionState _session = null;



       private void ExtractSessionStateRefs()

       {

              foreach(PropertyInfo inf in this.GetType().GetProperties())

                     inf.SetValue(this,this._session[inf.Name],null);

       }



       void ISmartSession.AbandonSession()

       {

              this._session.Abandon();

       }



       void ISmartSession.FlushSessionState()

       {
this._session.Clear();



      }



      void ISmartSession.PersistToSession()

      {

             foreach(PropertyInfo inf in this.GetType().GetProperties())

             {

                    this._session[inf.Name] = inf.GetValue(this,null);

             }

      }



      void ISmartSession.SetSessionState(HttpSessionState ses)

      {

             this._session = ses;

             this.ExtractSessionStateRefs();

      }




}

Here is the AcmeSmartSessionBase implementation (I included the person object so
you can see the class we are keeping in session state

public class AcmeSmartSession :
BasePageFramework.SmartState.SmartSessionBase

{

      private Person _person = null;



      public Person SelectedPerson

      {

             get{return _person;}
set{_person = value;}



       }

}

[Serializable()]
public class Person
{

       string _name = string.Empty;

       int _age = 0;

       int _weight = 0;



       public string Name

       {

              get{return _name;}

              set{_name = value;}

       }

       public int Age

       {

              get{return    _age;}

              set{_age = value;}

       }

       public int Weight

       {

              get{return _weight;}

              set{_weight = value;}

       }

}



Using the application state is similar except that we lock the instance before we mess
with it.
Here is the code for our default SmartApplicationStateBase (not too much different
from the SmartSessionBase aside from the thread sync stuff).
public class SmartApplicationBase : ISmartApplication

{



      [field: NonSerialized()]

      private HttpApplicationState _appState = null;



      #region ISmartApplication Members



      public void FlushAppState()

      {

           CheckInit();

           _appState.Clear();

      }



      public void PersistToAppState()

      {

           CheckInit();



           _appState.Lock();



            //update the derived smartapplication object to the
application state...

           foreach(PropertyInfo inf in this.GetType().GetProperties())

                 this._appState[inf.Name] = inf.GetValue(this,null);



           _appState.UnLock();

      }



      public void SetApplicationState(System.Web.HttpApplicationState
state)
{

             _appState = state;

            //extract the values out of application state so our smart
application object

             //has everything it needs to be referenced in the page

             _appState.Lock();



             foreach(PropertyInfo inf in this.GetType().GetProperties())

                    inf.SetValue(this,this._appState[inf.Name],null);



             _appState.UnLock();

      }



      private void CheckInit()

      {

             if(_appState == null)

                  throw new InvalidOperationException("The
SmartApplicationState must be initialized before it is accessed");

      }



      #endregion

}

Finally here is an example of how to use these objects (remember our master page
already set these up for us so they are already to go when we need them).

private void Button1_Click(object sender, System.EventArgs e)

{

      Classes.AcmeClass cls = new Classes.AcmeClass("Chase",30);
//create an object

       ((Master.AcmeSmartSession)this.SmartSession).Person = cls; //set
it into our smartsession instance

      this.SmartSession.PersistToSession(); //update our current smart
session to the session state
AcmeSmartQueryString sqs =
(AcmeSmartQueryString)this.SmartQueryString;

       sqs.Page = 2.ToString();

       sqs.SmartRedirect("TestOne.aspx");

}

And this is how you get to it when you need it…

AcmeSmartSession sss = (AcmeSmartSession)this.SmartSession;

       if(sss.Person != null)

       sss.Person.Name = "Bob";

I am not going to post anything about how to use the Application State stuff since it
works the exact same way as the SmartSession.

That is the smart state objects in a nutshell – I am currently working on ways to
make the persisting calls more efficient by adding an interface to check for a new /
dirty / clean object. I will post the enhancements to this framework as they become
available.

Mais conteúdo relacionado

Mais procurados

II - Angular.js app structure
II - Angular.js app structureII - Angular.js app structure
II - Angular.js app structureWebF
 
IV - CSS architecture
IV - CSS architectureIV - CSS architecture
IV - CSS architectureWebF
 
Building a Simple Mobile-optimized Web App Using the jQuery Mobile Framework
Building a Simple Mobile-optimized Web App Using the jQuery Mobile FrameworkBuilding a Simple Mobile-optimized Web App Using the jQuery Mobile Framework
Building a Simple Mobile-optimized Web App Using the jQuery Mobile FrameworkSt. Petersburg College
 
Introduction to Django CMS
Introduction to Django CMS Introduction to Django CMS
Introduction to Django CMS Pim Van Heuven
 
Great Responsive-ability Web Design
Great Responsive-ability Web DesignGreat Responsive-ability Web Design
Great Responsive-ability Web DesignMike Wilcox
 
Fronted From Scratch - Supercharge Magento page speed
Fronted From Scratch - Supercharge Magento page speedFronted From Scratch - Supercharge Magento page speed
Fronted From Scratch - Supercharge Magento page speedYousef Cisco
 
Web Components v1
Web Components v1Web Components v1
Web Components v1Mike Wilcox
 
Lightning Bolt for Communities 101
Lightning Bolt for Communities 101Lightning Bolt for Communities 101
Lightning Bolt for Communities 101Salesforce Admins
 
Developing Custom WordPress Themes for Clients
Developing Custom WordPress Themes for ClientsDeveloping Custom WordPress Themes for Clients
Developing Custom WordPress Themes for ClientsSteven Slack
 
ME vs WEB - AngularJS Fundamentals
ME vs WEB - AngularJS FundamentalsME vs WEB - AngularJS Fundamentals
ME vs WEB - AngularJS FundamentalsAviran Cohen
 
Building jQuery Mobile Web Apps
Building jQuery Mobile Web AppsBuilding jQuery Mobile Web Apps
Building jQuery Mobile Web AppsOperation Mobile
 
jQuery Conference Austin Sept 2013
jQuery Conference Austin Sept 2013jQuery Conference Austin Sept 2013
jQuery Conference Austin Sept 2013dmethvin
 
Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)Joao Lucas Santana
 
PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...
PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...
PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...Lincoln III
 
AEM 6.0 - Author UI Customization & Features
AEM 6.0 - Author UI Customization & FeaturesAEM 6.0 - Author UI Customization & Features
AEM 6.0 - Author UI Customization & FeaturesAbhinit Bhatnagar
 
Optaros Surf Code Camp Walkthrough 1
Optaros Surf Code Camp Walkthrough 1Optaros Surf Code Camp Walkthrough 1
Optaros Surf Code Camp Walkthrough 1Jeff Potts
 
Backbone.js with React Views - Server Rendering, Virtual DOM, and More!
Backbone.js with React Views - Server Rendering, Virtual DOM, and More!Backbone.js with React Views - Server Rendering, Virtual DOM, and More!
Backbone.js with React Views - Server Rendering, Virtual DOM, and More!Ryan Roemer
 
Responsive Theme Workshop - WordCamp Columbus 2015
Responsive Theme Workshop - WordCamp Columbus 2015Responsive Theme Workshop - WordCamp Columbus 2015
Responsive Theme Workshop - WordCamp Columbus 2015Joe Querin
 
My second word press plugin
My second word press pluginMy second word press plugin
My second word press pluginDonghyeok Kang
 
Refactor Large applications with Backbone
Refactor Large applications with BackboneRefactor Large applications with Backbone
Refactor Large applications with BackboneColdFusionConference
 

Mais procurados (20)

II - Angular.js app structure
II - Angular.js app structureII - Angular.js app structure
II - Angular.js app structure
 
IV - CSS architecture
IV - CSS architectureIV - CSS architecture
IV - CSS architecture
 
Building a Simple Mobile-optimized Web App Using the jQuery Mobile Framework
Building a Simple Mobile-optimized Web App Using the jQuery Mobile FrameworkBuilding a Simple Mobile-optimized Web App Using the jQuery Mobile Framework
Building a Simple Mobile-optimized Web App Using the jQuery Mobile Framework
 
Introduction to Django CMS
Introduction to Django CMS Introduction to Django CMS
Introduction to Django CMS
 
Great Responsive-ability Web Design
Great Responsive-ability Web DesignGreat Responsive-ability Web Design
Great Responsive-ability Web Design
 
Fronted From Scratch - Supercharge Magento page speed
Fronted From Scratch - Supercharge Magento page speedFronted From Scratch - Supercharge Magento page speed
Fronted From Scratch - Supercharge Magento page speed
 
Web Components v1
Web Components v1Web Components v1
Web Components v1
 
Lightning Bolt for Communities 101
Lightning Bolt for Communities 101Lightning Bolt for Communities 101
Lightning Bolt for Communities 101
 
Developing Custom WordPress Themes for Clients
Developing Custom WordPress Themes for ClientsDeveloping Custom WordPress Themes for Clients
Developing Custom WordPress Themes for Clients
 
ME vs WEB - AngularJS Fundamentals
ME vs WEB - AngularJS FundamentalsME vs WEB - AngularJS Fundamentals
ME vs WEB - AngularJS Fundamentals
 
Building jQuery Mobile Web Apps
Building jQuery Mobile Web AppsBuilding jQuery Mobile Web Apps
Building jQuery Mobile Web Apps
 
jQuery Conference Austin Sept 2013
jQuery Conference Austin Sept 2013jQuery Conference Austin Sept 2013
jQuery Conference Austin Sept 2013
 
Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)Desenvolvimento web com Ruby on Rails (parte 2)
Desenvolvimento web com Ruby on Rails (parte 2)
 
PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...
PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...
PrettyFaces: SEO, Dynamic, Parameters, Bookmarks, Navigation for JSF / JSF2 (...
 
AEM 6.0 - Author UI Customization & Features
AEM 6.0 - Author UI Customization & FeaturesAEM 6.0 - Author UI Customization & Features
AEM 6.0 - Author UI Customization & Features
 
Optaros Surf Code Camp Walkthrough 1
Optaros Surf Code Camp Walkthrough 1Optaros Surf Code Camp Walkthrough 1
Optaros Surf Code Camp Walkthrough 1
 
Backbone.js with React Views - Server Rendering, Virtual DOM, and More!
Backbone.js with React Views - Server Rendering, Virtual DOM, and More!Backbone.js with React Views - Server Rendering, Virtual DOM, and More!
Backbone.js with React Views - Server Rendering, Virtual DOM, and More!
 
Responsive Theme Workshop - WordCamp Columbus 2015
Responsive Theme Workshop - WordCamp Columbus 2015Responsive Theme Workshop - WordCamp Columbus 2015
Responsive Theme Workshop - WordCamp Columbus 2015
 
My second word press plugin
My second word press pluginMy second word press plugin
My second word press plugin
 
Refactor Large applications with Backbone
Refactor Large applications with BackboneRefactor Large applications with Backbone
Refactor Large applications with Backbone
 

Destaque

Formation of the Grand Canyon Starter
Formation of the Grand Canyon StarterFormation of the Grand Canyon Starter
Formation of the Grand Canyon Starterdouglasgreig
 
Edisi 10 April Aceh
Edisi 10 April AcehEdisi 10 April Aceh
Edisi 10 April Acehepaper
 
Make The Most Of Your Marketing Budget!
Make The Most Of Your Marketing Budget!Make The Most Of Your Marketing Budget!
Make The Most Of Your Marketing Budget!kathylynn
 
Frandsen Group References 2008
Frandsen Group   References 2008Frandsen Group   References 2008
Frandsen Group References 2008bluetech7
 
LTS Presentation: Live the Source Business ACCELERATION Model
LTS Presentation: Live the Source Business ACCELERATION ModelLTS Presentation: Live the Source Business ACCELERATION Model
LTS Presentation: Live the Source Business ACCELERATION Modeljptedeschi
 
Edisi 12 Des 09 Aceh
Edisi 12 Des 09 AcehEdisi 12 Des 09 Aceh
Edisi 12 Des 09 Acehepaper
 
Binder1 Aceh 28nov
Binder1 Aceh 28novBinder1 Aceh 28nov
Binder1 Aceh 28novepaper
 
Estrategias de 3 para telexfree
Estrategias de 3 para telexfreeEstrategias de 3 para telexfree
Estrategias de 3 para telexfreejose david
 
Edisi 25 Nasional
Edisi 25 NasionalEdisi 25 Nasional
Edisi 25 Nasionalepaper
 
Waspada Nasional 7 September
Waspada Nasional 7 SeptemberWaspada Nasional 7 September
Waspada Nasional 7 Septemberepaper
 
Edisi17oktnasioanal
Edisi17oktnasioanalEdisi17oktnasioanal
Edisi17oktnasioanalepaper
 
MFI Analysis: Cashpor
MFI Analysis: CashporMFI Analysis: Cashpor
MFI Analysis: Cashpordwueger
 
Improving Performance Intro 280709
Improving Performance Intro 280709Improving Performance Intro 280709
Improving Performance Intro 280709Robert Twiddy
 
Paul Potter Internet Exposure
Paul Potter Internet ExposurePaul Potter Internet Exposure
Paul Potter Internet ExposurePaul Potter
 
The Impact Of Decoys And Background Information On
The Impact Of Decoys And Background Information OnThe Impact Of Decoys And Background Information On
The Impact Of Decoys And Background Information OnFabioBuoncristiano
 
Excellence through creativity
Excellence through creativityExcellence through creativity
Excellence through creativitydouglasgreig
 
Presentatie Eindverhandeling
Presentatie EindverhandelingPresentatie Eindverhandeling
Presentatie EindverhandelingThisco
 
28des Aceh
28des Aceh28des Aceh
28des Acehepaper
 
Locals Slides - CityRank
Locals Slides - CityRankLocals Slides - CityRank
Locals Slides - CityRankAdi Buzgar
 

Destaque (20)

Formation of the Grand Canyon Starter
Formation of the Grand Canyon StarterFormation of the Grand Canyon Starter
Formation of the Grand Canyon Starter
 
Edisi 10 April Aceh
Edisi 10 April AcehEdisi 10 April Aceh
Edisi 10 April Aceh
 
Make The Most Of Your Marketing Budget!
Make The Most Of Your Marketing Budget!Make The Most Of Your Marketing Budget!
Make The Most Of Your Marketing Budget!
 
Frandsen Group References 2008
Frandsen Group   References 2008Frandsen Group   References 2008
Frandsen Group References 2008
 
LTS Presentation: Live the Source Business ACCELERATION Model
LTS Presentation: Live the Source Business ACCELERATION ModelLTS Presentation: Live the Source Business ACCELERATION Model
LTS Presentation: Live the Source Business ACCELERATION Model
 
Edisi 12 Des 09 Aceh
Edisi 12 Des 09 AcehEdisi 12 Des 09 Aceh
Edisi 12 Des 09 Aceh
 
Binder1 Aceh 28nov
Binder1 Aceh 28novBinder1 Aceh 28nov
Binder1 Aceh 28nov
 
Estrategias de 3 para telexfree
Estrategias de 3 para telexfreeEstrategias de 3 para telexfree
Estrategias de 3 para telexfree
 
Rm3-A Device
Rm3-A DeviceRm3-A Device
Rm3-A Device
 
Edisi 25 Nasional
Edisi 25 NasionalEdisi 25 Nasional
Edisi 25 Nasional
 
Waspada Nasional 7 September
Waspada Nasional 7 SeptemberWaspada Nasional 7 September
Waspada Nasional 7 September
 
Edisi17oktnasioanal
Edisi17oktnasioanalEdisi17oktnasioanal
Edisi17oktnasioanal
 
MFI Analysis: Cashpor
MFI Analysis: CashporMFI Analysis: Cashpor
MFI Analysis: Cashpor
 
Improving Performance Intro 280709
Improving Performance Intro 280709Improving Performance Intro 280709
Improving Performance Intro 280709
 
Paul Potter Internet Exposure
Paul Potter Internet ExposurePaul Potter Internet Exposure
Paul Potter Internet Exposure
 
The Impact Of Decoys And Background Information On
The Impact Of Decoys And Background Information OnThe Impact Of Decoys And Background Information On
The Impact Of Decoys And Background Information On
 
Excellence through creativity
Excellence through creativityExcellence through creativity
Excellence through creativity
 
Presentatie Eindverhandeling
Presentatie EindverhandelingPresentatie Eindverhandeling
Presentatie Eindverhandeling
 
28des Aceh
28des Aceh28des Aceh
28des Aceh
 
Locals Slides - CityRank
Locals Slides - CityRankLocals Slides - CityRank
Locals Slides - CityRank
 

Semelhante a .NET 1.1 Base Page Framework Article

Website Development Guidelines
Website Development GuidelinesWebsite Development Guidelines
Website Development GuidelinesAmit Kute
 
Master Pages In Asp.net
Master Pages In Asp.netMaster Pages In Asp.net
Master Pages In Asp.netparallelminder
 
Implementation of gui framework part2
Implementation of gui framework part2Implementation of gui framework part2
Implementation of gui framework part2masahiroookubo
 
Sps redmond 2014 deck
Sps redmond 2014 deckSps redmond 2014 deck
Sps redmond 2014 deckDorinda Reyes
 
SharePoint Exchange Forum - 10 Worst Mistakes in SharePoint Branding
SharePoint Exchange Forum - 10 Worst Mistakes in SharePoint BrandingSharePoint Exchange Forum - 10 Worst Mistakes in SharePoint Branding
SharePoint Exchange Forum - 10 Worst Mistakes in SharePoint BrandingMarcy Kellar
 
Blooming SharePoint Design
Blooming SharePoint DesignBlooming SharePoint Design
Blooming SharePoint DesignKathy Hughes
 
Joomla Template Tutorial
Joomla Template TutorialJoomla Template Tutorial
Joomla Template Tutorialbrighteyes
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMAdobeMarketingCloud
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMAdobeMarketingCloud
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMconnectwebex
 
LvivCSS: Web Components as a foundation for Design System
LvivCSS: Web Components as a foundation for Design SystemLvivCSS: Web Components as a foundation for Design System
LvivCSS: Web Components as a foundation for Design SystemVlad Fedosov
 
"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcher"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcherlottepitcher
 
KharkivJS: Flaws of the Web Components in 2019 and how to address them
KharkivJS: Flaws of the Web Components in 2019 and how to address themKharkivJS: Flaws of the Web Components in 2019 and how to address them
KharkivJS: Flaws of the Web Components in 2019 and how to address themVlad Fedosov
 
2013 SPFest - Customizing Sites and Pages in SharePoint 2013
2013 SPFest - Customizing Sites and Pages in SharePoint 20132013 SPFest - Customizing Sites and Pages in SharePoint 2013
2013 SPFest - Customizing Sites and Pages in SharePoint 2013Wes Preston
 
Introducing asp.net web pages 2
Introducing asp.net web pages 2Introducing asp.net web pages 2
Introducing asp.net web pages 2Uh-meet Thapa
 
SharePoint Site templates, site definitions, feature stapling
SharePoint Site templates, site definitions, feature staplingSharePoint Site templates, site definitions, feature stapling
SharePoint Site templates, site definitions, feature staplingSalaudeen Rajack
 
Wordpress workflow for an agency world
Wordpress workflow for an agency worldWordpress workflow for an agency world
Wordpress workflow for an agency worldChris Lowe
 

Semelhante a .NET 1.1 Base Page Framework Article (20)

Website Development Guidelines
Website Development GuidelinesWebsite Development Guidelines
Website Development Guidelines
 
Master Pages In Asp.net
Master Pages In Asp.netMaster Pages In Asp.net
Master Pages In Asp.net
 
Implementation of gui framework part2
Implementation of gui framework part2Implementation of gui framework part2
Implementation of gui framework part2
 
Sps redmond 2014 deck
Sps redmond 2014 deckSps redmond 2014 deck
Sps redmond 2014 deck
 
SharePoint Exchange Forum - 10 Worst Mistakes in SharePoint Branding
SharePoint Exchange Forum - 10 Worst Mistakes in SharePoint BrandingSharePoint Exchange Forum - 10 Worst Mistakes in SharePoint Branding
SharePoint Exchange Forum - 10 Worst Mistakes in SharePoint Branding
 
Blooming SharePoint Design
Blooming SharePoint DesignBlooming SharePoint Design
Blooming SharePoint Design
 
Joomla Template Tutorial
Joomla Template TutorialJoomla Template Tutorial
Joomla Template Tutorial
 
SiteMesh
SiteMeshSiteMesh
SiteMesh
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEM
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEM
 
Build single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEMBuild single page applications using AngularJS on AEM
Build single page applications using AngularJS on AEM
 
LvivCSS: Web Components as a foundation for Design System
LvivCSS: Web Components as a foundation for Design SystemLvivCSS: Web Components as a foundation for Design System
LvivCSS: Web Components as a foundation for Design System
 
Fame
FameFame
Fame
 
"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcher"Umbraco MVC - a journey of discovery" - Lotte Pitcher
"Umbraco MVC - a journey of discovery" - Lotte Pitcher
 
KharkivJS: Flaws of the Web Components in 2019 and how to address them
KharkivJS: Flaws of the Web Components in 2019 and how to address themKharkivJS: Flaws of the Web Components in 2019 and how to address them
KharkivJS: Flaws of the Web Components in 2019 and how to address them
 
2013 SPFest - Customizing Sites and Pages in SharePoint 2013
2013 SPFest - Customizing Sites and Pages in SharePoint 20132013 SPFest - Customizing Sites and Pages in SharePoint 2013
2013 SPFest - Customizing Sites and Pages in SharePoint 2013
 
10 things to remember
10 things to remember10 things to remember
10 things to remember
 
Introducing asp.net web pages 2
Introducing asp.net web pages 2Introducing asp.net web pages 2
Introducing asp.net web pages 2
 
SharePoint Site templates, site definitions, feature stapling
SharePoint Site templates, site definitions, feature staplingSharePoint Site templates, site definitions, feature stapling
SharePoint Site templates, site definitions, feature stapling
 
Wordpress workflow for an agency world
Wordpress workflow for an agency worldWordpress workflow for an agency world
Wordpress workflow for an agency world
 

.NET 1.1 Base Page Framework Article

  • 1. Building A Better ASP.NET 1.1 Basepage Framework - Part 1 - Introduction This 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 posts 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 • Pros o No need to recreate the look and feel for each page o You can see the look and feel in the designer (big deal) :P • Cons o You have to cut and paste the HTML into all your pages o You have to maintain all the pages on the site to make the simplest change to your site (not much of an advantage if you ask me) Comments: This is the approach I have see 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 • Pros o Simply drag and drop your UI into your new pages and your done! • Cons o Difficult to maintain the site because if you need to remove/add user controls in the Base layout you have to touch every single page in the site o You cannot see the complete look and feel in the designer (again – big deal) 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 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.
  • 2. Pros o Now were getting somewhere! We no longer have to maintain the main look and feel across the entire site from each individual page – nor do we need to worry about changes to what elements are added to the main UI because we can change it all from one place • Cons o Very difficult to maintain from a designer perspective (there is no designer support for this approach available) try telling that to your web design team and see what happens. o You will need to recompile the site if the slightest HTML changes need to be made to the look and feel of the site. o You should not do this in the OnInit() because it does not guarantee that your controls will be in place at all phases of execution within your ASP.NET pipeline, the code for generating the base page should be in the CreateChildControls() call. You can force CreateChildControls() to be called by calling EnsureChildControls(). 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. • Pros o Designers can alter the HTML of each user control without recompiling the Website DLL (unless they add server controls). o You can make changes to the site’s look and feel without having to change any Base page code (in most cases). • Cons o You still have to deal with hardcoding the layout of your controls into the Base page (not cool). 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 the 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 • Pros
  • 3. o Designers can alter the layout and look and feel of your site from not only the separate UserControls (which I call base controls or Base controls) but also from the UI layout, which is just another UserControls with placeholders o All information about how to generate the HTML header and what styles / scripts to link in and how to set up the body tag can be altered from the web.config • Cons Still – the full support of having the Base page elements viewable in the designer is not available (you’ll just have to wait for the MasterPages in ASP.NET 2.0 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 these posts. So why write these posts? 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 still 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 I 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 post I will describe the steps to get started on your own BasePage Framework implementation. 1: Create a Class Library Project call it “BasePageFramework”. 2: Add the System.Web reference. 3: Create a SuperBasePage class and inherit from System.Web.UI.Page. 4: Create a class and call it BasePageConfigurationHandler. Implement the System.Configuration.ICustomConfigurationHandler interface. 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 Basepages). Add the following XML to the Schema… <configSections> <section name="base.configuration" type="BasePageFramework.Configuration.BasePageSectionHan dler,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"/>
  • 4. <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> Building A Better ASP.NET 1.1 Basepage Framework - Part Two - Implementing the Configuration Handler I 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 basepages hard code this stuff which always makes me shudder. (As it should everyone because hardcoding things like this should be avoided). IMHO it makes sence to place this 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 <add key=““ value=““/> settings available in the <appsettings> section because I personally think this is less intuitive as to what the settings are for. Plus it would be nice to not clutter our appsettings for this application with configuration information that is going to be used across 1 to many sites. To solve this problem we create a custom configuration section for our Base Page Framework. That is the subject of this post.
  • 5. 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 IConfigurationSectionHandler interface which is part of the System.Configuration namespace. This is really easy to do and if you know how to parse XML you are 4/5th of the way there. The IConfigurationSectionHandler interface has one method called Create() here is the prototype for Create and what each argument is intended to provide. public object Create(object parent, object configContext, XmlNode section) Returns object : this object can be anything you want I like to create a separate configuration class that holds all my settings and return it so I can cache it for later use. This example will be doing the very same thing. object parent The parent config section. object configContext An HttpConfigurationContext object. This is only passed to us when we call it from ASP.NET, as you probably guessed if you do this out of a windows app via an app.config file this parameter will be null. XmlNode section This is our custom configuration section. We will be parsing this to get our configuration settings out of the config file. 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 BasePageConfigurationSectionHandler. This class will be our configuration handler so implement the IConfigurationSectionHandler interface. Here is the code. 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 BasePagesConfiguration class. If you look at the schema for our
  • 6. configuration you will see that there can be 1:n page elements. This is because you can have more than one base page in your site. So you if you guessed that BasePagesConfiguration is a collectionbase object then you guessed right. We are going to need to implement this object next. Here is the code. 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 BasePageConfiguration object via a key value. I am not too concerned with how quickly the collection returns individual configuration objects so I am using a simple loop. The reason we don’t really care about performance here is because our base page will internally cache it’s configuration object so it does not have to keep going out to the config file everytime it gets requested. Finally we get to the configuration object we are going to aptly call this BasePageConfiguration. Create the BasePageConfiguration class in your project here is the code. I am going to assume that you know how to parse XML documents so I am not going to go into detail on how that works. I will just explain what each field of this object is for. 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();
  • 7. 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");
  • 8. 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;}} }
  • 9. This is the ID of our base page. Each base page in the system must have a unique name so it’s configuration can be located in the config file (more on this later when we get into implementing the actual basepagebase). private NameValueCollection _bodyAttributes These are the individual body attribute tags that will go into each and every one of our pages. The are injected into the HTML in the basepagebase OnAddParsedSubObject() call (many thanks to Michael Earls for showing me the light on this one). private string _defaultTitle This is the default title that will be used on all pages of our site. Should the HTML designer not put anything into the <Title> tag we will replace that blank value with this one. private NameValueCollection _scriptFiles This is a list of script links that will be placed inside the <head> tags. private NameValueCollection _cssFiles This is a list of Stylesheet links that will be placed inside the <head> tags. private NameValueCollection _metaTags This is a list of default metatags that will appear in all pages of our site. Finally as you can see the configuration is a read only object. This is logical because it will only get it’s 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 GetConfig() in your ConfigurationSettings object. Configuration.BasePagesConfiguration baseConfiguration =(Configuration.BasePagesConfiguration)System.Configuration.Configurati onSettings.GetConfig("base.configuration"); Returns: the appropriate configuration object based on your section in the configSections tag. See the following snippet taken from the schema we created in part one. <configSections> <section name="base.configuration" type="BasePageFramework.Configuration.BasePageSectionHandler,BasePageFr amework"/> </configSections> The object that gets returned here is whatever you decided to return from your IConfigurationSectionHandler.Create implementation in this case it is our ReadOnlyCollectionBase “BasePagesConfiguration“ object that contains the individual BasePageConfiguration objects for our site.
  • 10. 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 IConfiguraitonSectionHandler interface along with the name of the assembly this object resides in separated by a comma. If you are referencing a strong named assembly then you need to put all four parts of the assembly name into the type attribute like this (taken from System.dll) … <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 SuperBasePage itself. To close in part 4 I am going to show you how to throw in some extra features that wrap standard site state management the objects are classes that implement the ISmartQueryString, ISmartSession, and ISmartApplication interfaces. Building A Better ASP.NET 1.1 Basepage Framework - Part Three - Implementing the SuperBasePage I am going to continue this at the point where we left off creating the BasePageConfiguration class. In this post are going to implement the SuperBasePage that will be used in all derived Base pages of your site. It is useful to have multiple Base pages in your site because you might want to have a layout for printer friendly pages, popup windows, dialogs, etc… This one 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 post so there will be some decent docs to go with it. The SuperBasePage is actually two separate classes and a user control that implements a custom interface all working together to form a template. They are declared as follows. 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; …
  • 11. These three items when properly used will not only allow us to quickly create Basepages 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 / Basepage concepts where you need to remove the HTML, HEAD, TITLE, BODY, and FORM tags in order to be useable. We will not be doing that here. It is important that we keep that part intact for two reasons, 1: Designers can understand it better so you do not need to worry about whether or not they will replace these tags in error and more importantly 2: Keeping these tags in each page allows us to override what is placed in the web.config (like trace directives etc… our framework follows the same pattern). To start with we need to look at the existing objects at our disposal in the System.Web Namespace, the ones we are interested in right now are System.Web.UI.Page and System.Web.UI.HtmlControls.HtmlForm. The later of the two is what we will be worrying about first. The HtmlForm when set with a server tag becomes the heart of the Ubiquitous ASP.NET webform. You cannot instantiate server controls on your page without it. Herein lies our problem, how can we get our form which we have to define on each page to behave the same by displaying the same outlying content with unique content for each page, and more importantly, how do we get the content from our page to actually nest itself within this form’s content area. What I have seen done in past Base pages I have developed is to “move” the content from each of their pages to a specified “content area” in their Base page class. We are still doing that here but in a more discreet way. And, as I have already mentioned, most of the examples force you to delete the HTML header and footer along with the Form tag from your page before using it (try explaining that to a web-designer and see what they do). 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 HtmlForm and change it into a form that has the following functionality… 1: Has a reference to a template that defines the layout for all our pages 2: This form needs to know how to contruct it in such a way that when you pass it an existing form, it can take the place of the old form with one important difference – It will have functionality for placing our individual pages content where the it all belongs. So in essence instead of our BasePage highjacking the content of each page, our Base page receives an htmlform and makes it work in such a way that allows us to set content wherever we want from a given template that the form will initialize when it gets created. You will notice that our BaseServerForm is nothing more than a plain old HtmlForm with a template reference. The following is the code for our Base server form… public class BaseServerForm : HtmlUI.HtmlForm { private WebCtlUI.PlaceHolder _uiTemplatePlcHldr = new WebCtlUI.PlaceHolder(); internal BaseServerForm(HtmlUI.HtmlForm oldFrm,IBaseTemplate
  • 12. 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.Contr ols[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.Insta nce).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 HtmlForm object and a reference to something called an IBaseTemplate. This is how you will implement the IBaseTemplate in your code… 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
  • 13. 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 BasePage’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 BasePage without going into the code but instead by using the designer. That is where using a UserControl (that implements our IBaseTemplate interface) Comes in handy. 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 IBaseTemplate interface comes in to play. As you can see in the above example, we define a placeholder where our derived pages content will go in the Base page and return it through this interfaces only property “ContentArea” (which coincidentally returns a placeholder for our content). Now our BasePage will have a contract with the UserControl that says “I will assume responsibility of locating the controls where they belong so you don’t have too”. You will see later on in the Base page definition why we need to expose the other placeholders in our usercontrol to the outside world via properties as I have done in the above example. The BaseServerForm class doesn’t do much other than encapsulate the movement of our pages content to the template’s content area (and perform the role of server form) so beside loading a UI template, it is basically just a server form with a layout defined. I should also note that we do not want anything but the Basepage creating a Baseserverform, so we hide the constructor from the outside world by declaring it internal. This is because the BasePage is the only object that should ever need to crate a BaseServerForm. However, we do want developers to be able to reference the form in the case they decide to change something programmatically before the request is handled. I had to resort to using reflection to rip apart the old HtmlForm’s private values and copy them to our BaseServerForm, we could of used a “has-a” relationship with the HtmlForm but that seemed a bit on the clunky side, therefore I made it an is-a relationship to make it’s relationship with our pages seamless. Also not that it is critical that we move the control tree and attributes of the old htmlform to our new BaseServerForm before we do use reflection to get any internal values, we can get to the control tree just fine, we don’t need to use reflection to get to it. I normally do not like using reflection unless absolutely required, I like to think of reflection like I think of Duct Tape, it’s a tool that has a noble purpose… But when used “creatively” you can end up really shooting yourself in the foot (if you will forgive the old C cliché).
  • 14. Now that we have our BaseServerForm class defined we need to define the superbasepage from which we will derive all our sites base pages. Here is the code… public class SuperBasePage : WebUI.Page { #region Constants private const int LG_STR_BUFFER_SZ = 512; //large string builder initial buffer siz private const int MD_STR_BUFFER_SZ = 256; //medium string builder inital buffer size private const int SM_STR_BUFFER_SZ = 128; //small (default) string builder buffer size 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 private WebCtl.PlaceHolder _serverFormPlcHldr = new WebCtl.PlaceHolder(); //our form sits in this placeholder private string _title = string.Empty; //the title of the page (displayed in the caption bar) 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{
  • 15. 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); BasePreRender(this._baseForm); //allows us to set the base page state from the child page before any rendering of the base page occurs LoadTemplatePanels(); //call the virutal method to allow the base page to set the appropriate usercontrols into the base template } protected override void OnInit(EventArgs e) { this.EnsureChildControls();
  • 16. 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); obj = this._baseForm; //replace the object reference with our own "base server form" } 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 -
  • 17. //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.Leng th); int endTitleTagOffset = htmlHeader.Text.ToUpper().IndexOf(CLOSE_TITLE_TAG,0,htmlHeader.Text.Len gth); 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._co nfig.DefaultTitle); } else { int openHeadTagOffset = htmlHeader.Text.ToUpper().IndexOf(OPEN_HEAD_TAG,0,htmlHeader.Text.Lengt h); //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);
  • 18. subHtml.Append(OPEN_TITLE_TAG); subHtml.Append(_config.DefaultT itle); 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.T oString().Length); htmlBuilder.Replace(CLOSE_HEAD_TAG,GetWebConfigLinks(htmlBuilder.ToStri ng())); RenderBodyTagLinks(htmlBuilder); //replace the text in the literal control with our new header htmlHeader.Text = htmlBuilder.ToString(); } } }
  • 19. 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");< SPAN> 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(" ");
  • 20. 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) {
  • 21. 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("rnt"); 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("rntt"); htmlBuilder.Append(" htmlBuilder.Append("NAME=""); htmlBuilder.Append(metaTag); htmlBuilder.Append("""); htmlBuilder.Append(" CONTENT=""); htmlBuilder.Append(_config.MetaTags.Get(metaTag)); htmlBuilder.Append(""/>"); } } }
  • 22. private void GetStyleSheetLinks(System.Text.StringBuilder scriptLinks) { foreach(string key in this._config.StyleSheets.Keys) { scriptLinks.Append("rntt 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("rntt "); } } #endregion #region Utility Methods
  • 23. 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.Configurat ion.ConfigurationSettings.GetConfig("base.configuration"); if(baseConfiguration == null)
  • 24. 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),Sys tem.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
  • 25. /// 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)
  • 26. {} /// /// 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 SuperBasePage 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)
  • 27. { 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 LoadBaseUITemplate call 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 }
  • 28. #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 AddParsedSubObject() and the AddChildControls() virtual methods. This is because we only need to use the AddParsedSubObject() method to do one thing. Intercept our html header and html form before it gets added to our page. I got this Idea from a Micheal Earls (a fellow Magenicon from Atlanta) check out his blog here http://www.cerkit.com. I have seen this method overriden in server controls for child control tags but not in a page class like this. I thought it was very clever use of the method and it solves the problem of having to remove the outer html from around our content area. Now that we have the outer HTML intact it also gives us the opertunity to override the Base
  • 29. page configuration at the page level thus imitating the web.config settings/page directive pattern that currently exists in ASP.NET. For those of you unfamiliar with this method. It gets fired whenever a handler encounterers a child element. In the case of the System.Web.Page class it gets fired a total of three times. 1 Once when it encounters the tag 2 Once when it encounters the tag 3 And one more time when it encounters the tag 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 get our custom configuration settings and place them into the header. This part of the handler needs to really scream so I struggled for awhile on what would be the best way, I have used HtmlWriters in the past but I thought for this it was too much overhead since we are already using one in the render call, plus I am forced to parse all the HTML in order to make that approach effective (so clearly it is not an option here). After some experimentation I decided to go with StringBuilders since they are faster than vanilla string concatenation (in fact the performance of the StringBuilder is comparable to using the old memcpy() call in C). To truly take advantage of the StringBuilder’s performance you need to estimate how much space you will need ahead of time, if you don’t the StringBuilder will be moving a lot of character memory around when it has to resize itself internally to make more room I use a combination of the default HTML content along with what is in the web.config to calculate a good initial size for my StringBuilder’s buffer. The reason this is so important is because when the stringbuilder reaches its limit during an append(), it creates a new buffer twice the size of the old one and copies all the character data over to the new buffer (then destroys the old buffer) all this movement of character data negates the performance gains of using a stringbuilder (although it is still probably faster than plain old string concatenation operators). Regardless, it’s better to be safe than sorry IMHO. We are doing some initialization in a the CreateChildControls override as well. We do this to ensure that these methods are only called once when the request gets handled. This is done by calling EnsureChildControls() from our init() call. This call “ensures” that our child controls get created (in this case we are initializing the SmartQueryString and SmartSessionState) Since they are not critical to using the Base page and since I am trying to keep this short, I will go into more detail on these two classes in the final post. The virtual methods in the SuperBasePage are to be overridden in our derived SuperBasePage class. Using these virtual methods allows us to define how and what our Base page loads when it is requested. Here is what each method does… protected virtual void InitializeConfiguration(ref ConfigLoadingArgs configParams)
  • 30. Override this method so we can tell the BasePage what page configuration to use (remember that we have more than one page element in our config section? This is where we specify what section to use). In this example the BasePage name attribute we want from our configuration is “AcmeBase”. We are passing the configParams argument by reference so we can set it in our override (in the derived base page). Here is an example of how to implement it protected override void InitializeConfiguration(ref BasePageFramework.Configuration.ConfigLoadingArgs configParams) { configParams.BasePageID = "AcmeBase"; } This in turn will tell our Super Base page to load settings from the following page config section… <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> The argument is passed by reference so we can set the BasePage name from our derived Base page class please note that if the configuration element does not exist I
  • 31. will throw you an exception (as you can see in the SuperBasePage code). You can also see that I am caching the settings (I do this for two reasons) 1: If I want to use the same config for more than one page – the configuration will only be created in memory once and shared with all pages that use it. 2: We do not want to parse the configuration on every request as this will cause a serious performance hit. Here is the ConfigLoadingArgs class that is getting passed to our derived page from the SuperBasePage through this virtual method. public class ConfigLoadingArgs { private string _basePageID = string.Empty; internal ConfigLoadingArgs() {} public string BasePageID { get{return _basePageID;} set{_basePageID = value;} } } protected virtual void BasePreRender(BaseServerForm baseForm) This method gets called in our create child controls call before we load any templates in to our Base UI. I have never needed to use this yet myself but I thought it may be necessary for someone to access the BaseServerForm for this page before it loads any controls. This is where you will do it. protected virtual void LoadBaseUITemplate(ref BasePageFramework.IBaseTemplate tmplt) This is where we pull in our main user control (that implements the IBaseTemplate interface) that will provide us with a layout for all the pages in our site. Internally when the BaseServerForm is loaded and initialized – it will use the IBaseTemplate interface to move the content to the appropriate area. Here is how I use it in my derived Base page class – the beauty here is that we are not required to load every single control into all the areas of the main template, you
  • 32. can have some static and some dynamically loaded (in the load template panels call you will see how we dynamically load controls which we usually do when we want our derived base page to subscribe to a given controls events. protected override void LoadBaseUITemplate(ref BasePageFramework.IBaseTemplate tmplt) { tmplt = (IBaseTemplate)LoadControl("~/BaseTemplateControl.ascx"); } protected virtual void LoadTemplatePanels() This override allows us to dynamically load controls into the areas of our Base template after the Base template UI and form are loaded and initialized. The reason you may need to do this is so you can attach events and subscribe to them in your derived Basepage class (like navigation postbacks, logout requests, etc…). Remember I told you earlier that I was going to let you in as to why I was exposing all the placeholders in my BaseTemplate user control? You can see why in the following snippet of code. I can get to those place holders in my derived Base page. There is a catch however, you need to downcast the initialized IBaseTemplate to whatever class your MainTemplate user control type is within this override before you can get to the placeholder properties. Here is how I use it (I am including an Event example so you can see what I want my Base page to do when the event gets fired)… protected override void LoadTemplatePanels() { this.BaseForm.BaseTemplate.Header.Controls.Add(LoadControl("~/BaseContr ols/AcmeHeader.ascx")); _leftNavCtl = (LeftNav)LoadControl("~/BaseControls/LeftNav.ascx"); this.BaseForm.BaseTemplate.LeftNav.Controls.Add(_leftNavCtl ); _leftNavCtl.OnNavEvent += new NavEventHandler(OnNavigationEvent); } private void OnNavigationEvent(object sender,NavEventArg e)
  • 33. { this.SmartQueryString.SmartRedirect(e.NavURL); } These are just examples I plan to release a full project with examples on how to do everything at a future date. These last two involve the SmartQueryString and SmartSession interfaces. The smart handler interfaces give us the ability to strongly type our querystring, session and application objects. I will go into more detail on the next post on what these methods are used for… protected virtual void InitSmartQueryString(ref ISmartQueryString isqs) protected virtual void InitSmartSession(ref ISmartSession ises) protected virtual void InitSmartApplication(ref ISmartApplication iapp) Other methods in the Base page base… private string ParseRootPath(string path) This method ensures that we always know where in the site our controls/resources/etc… are located in relation to the derived page. It uses the standard ASP.NET recognition of the tilde “~” as the delimiter for our application root. This is important because we may be running our application out of a virtual directory (in the case of running it from the root of a website we could have just used the standard forward slash “/” in front of our links to specify the site’s root). Don’t forget that this page can be used by all pages of your site in any subdirectory, which also means that since your pages are in different directories / subdirectories, you need this so your pages can always find the user controls and other site resources, etc...). private void InitFromConfiguration(string pageName) This is where we get our custom configuration settings. Remember that one of the features of the framework is that the individual pages can override the settings you put in the web.config. That way if you want the same layout (but a different backcolor,script,metatag,etc…) you can do it without having to hard code it, just do it the way any web designer would do it, put it in the hypertext where it belongs. If you don’t override it, then the page will use the settings from the web.config. This follows the same “machine/web/page” pattern used in all the directives of ASP.NET. Well, that was a lot to say (and for you to read) I tried to keep it short since this is just a blog post, so if you have any questions just ask and I will be happy to answer. In the last post of this series I will talk about the Smart Handler objects (SmartSession, SmartQueryString, SmartApplication) and how you can create your own base handlers to persist values differently. To explain what they are for, they allow us to manage our querystring, etc… from a within our Base page (like the response and request objects already do – these are strongly typed and easy to use). They have made my life infinitely easier.
  • 34. Building A Better ASP.NET 1.1 Basepage Framework - Part Four - Implementing the Smart Session Objects In this final post (for now anyway until someone gives me some better ideas or I wake up in the middle of the night with one of my own) I am going to describe how to implement the Smart State Objects that we can get data from via the master page. One of the things I really don’t like to do is having to hardcode strings to find stuff in a generic collection object and then rely on that object being of a specific type before I can actually use it. When I am working on the project independently it is not as much of a problem, but when it grows to more than a couple of developers it gets tough to keep track of the keys (unless of course you step through the entire application with trace turned on so you can monitor additions and removals from our state objects. This gets even worse when dealing with querystring data. You can add and remove things from the query string anywhere and it does not have to be consistent so when you redirect to another page on the web app, you are assuming that the next page is always going to have access to the query string elements you are going to need and worse, that the values that they represent are not going to change in context. My solution to this is to wrap these objects in my own smart session classes. We had a monster problem synching up the query string that we were using at an Ecommerce project once, and the minute the scope shifted and the client stated that they wanted another feature (and another querystring because this site had to do all it’s state maintenance through the query string) we had to go through every page in our purchasing and payment process, etc… to make sure this element was added when it was needed (and worse – they were using string concatenation operators to do it on every single redirect in the site). Yuck!!! So I took a little time and wisely spent some time coming up with a better way to handle this problem, the solution is the ISmartQueryString, ISmartSession and ISmartApplication interfaces. Here is an example of the Interfaces followed by what they allow us to do… public interface ISmartSession { void SetSessionState(System.Web.SessionState.HttpSessionState ses); void FlushSessionState(); void AbandonSession(); void PersistToSession(); } public interface ISmartQueryString {
  • 35. void SetQueryStringInfo(System.Web.UI.Page webPage); void SmartRedirect(string url); } public interface ISmartApplication { void FlushAppState(); void PersistToAppState(); void SetApplicationState(System.Web.HttpApplicationState state); } ISmartQueryStirng: Allows us to encapsulate the query string in one spot in our code – we are always going to be assured of an elements exsistence (with default values set) so we do not need to write the code to check this in every single page. We will implement this interface in our SmartQueryStringBase class (more on that in a bit). Here are the methods that need to be implemented in the ISmartQueryString void SetQueryStringInfo(System.Web.UI.Page webPage); This is where we pass our http handler (the page object in the case of ASP.NET) to our smart query string base. We need to pass the whole thing because we are going to be using several items out of not only the request object but also the response object as you will see in a bit. void SmartRedirect(string url); This is the magic call that keeps our querystring alive in the system. Yes your developers can go ahead and just use the Response call directly and break it but that is easily remedied if you find it somewhere in the code (try using a Find on Response. And you will find it in no time!) Another thing you could do is use FXCop to keep it from happening if you need to automate the restriction. Then you can do whatever you do to the misguided developer and keep in on focus with the rest of the team. Now a framework wouldn’t be complete with an out of the box implementation of this interface so I am providing one. In this interface I am using reflection to get the values out of my SmartQueryString object (I am going to call it AcmeSmartQueryString() for lack of a better name). You can see in the following code how reflection takes the property values of our smart querystring and moves the value to our querystring. I am also putting a prefix on this “sqs” for “Smart Query String”. This tells our smart query string class that there is a query string present and it can begin parsing it out back into our AcmeSmartQueryString() object when the MasterPage is initialized during a request. Here is the code… [Serializable()]
  • 36. public abstract class SmartQueryStringBase : ISmartQueryString { [field: NonSerialized()] private System.Web.UI.Page _page = null; public void SetQueryStringInfo(System.Web.UI.Page webPage) { _page = webPage; ExtractQueryStringInfo(); } public void SmartRedirect(string url) { //build a querystring from the internal elements of this class and redirect the user string qs = GenerateQueryString(); if(qs != string.Empty) _page.Response.Redirect(ParseRootPath(url) + "? sqs=true&" + qs); else _page.Response.Redirect(ParseRootPath(url)); } //use reflection to take our objects field values and format it into a querystring private string GenerateQueryString() { PropertyInfo [] props = this.GetType().GetProperties(); StringBuilder sb = new StringBuilder();
  • 37. foreach(PropertyInfo prop in props) { BasePageFramework.Attributes.SmartQueryStringKeyAttribute [] attb = (BasePageFramework.Attributes.SmartQueryStringKeyAttribute []) prop.GetCustomAttributes(typeof(BasePageFramework.Attributes.SmartQuery StringKeyAttribute), true); //use the name if the attribute was omitted from the property if(attb.Length == 0) sb.Append(prop.Name + "=" + prop.GetValue(this,null).ToString() + "&"); else sb.Append(attb[0].KeyName + "=" + prop.GetValue(this,null).ToString() + "&"); } return sb.ToString(); } public override string ToString() { return "?sqs=true&" + GenerateQueryString(); } private string ParseRootPath(string path) { if(path.IndexOf('~') == 0)
  • 38. { 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 ExtractQueryStringInfo() { string sqsElement = _page.Request.QueryString["sqs"]; if(sqsElement != null && sqsElement != string.Empty) { PropertyInfo [] props = this.GetType().GetProperties(); foreach(PropertyInfo prop in props) { BasePageFramework.Attributes.SmartQueryStringKeyAttribute [] attb = (BasePageFramework.Attributes.SmartQueryStringKeyAttribute [])prop.GetCustomAttributes(typeof(BasePageFramework.Attributes.SmartQu eryStringKeyAttribute),true);
  • 39. if(attb.Length == 0) prop.SetValue(this,GetFieldValue(prop.Name),null); else prop.SetValue(this,GetFieldValue(attb[0].KeyName),null); } } } private object GetFieldValue(string elementName) { string retval = _page.Request.QueryString.Get(elementName); if(retval != null) return retval; else return string.Empty; } } Finally, if you look you can see what the SmartRedirect does. It’s nice because you only need supply the smart redirect with your URL where you are going and the smart query string does the rest of the work for you! Not too shabby! Now if you are looking closely you will also see that I am using an attribute to generate the key name for our query string. This is the SmartQueryStringKeyAttribute(). We will apply this attribute to our AcmeSmartQueryString object’s properties so we can keep the key values short and sweet in our query strings output. And here is the attribute class we are using to create our key value output… public class SmartQueryStringKeyAttribute : System.Attribute { private string _queryStringKey = string.Empty;
  • 40. public SmartQueryStringKeyAttribute(string queryStringKey) { _queryStringKey = queryStringKey; } public string KeyName { get{return _queryStringKey;} } } Finally – here is the AcmeSmartQueryString class (our derived smart query string object that holds our smart query strings values for the app). public class AcmeSmartQueryString : BasePageFramework.SmartState.SmartQueryStringBase { private string _pgNum = string.Empty; private string _tabIdx = string.Empty; private string _userName = string.Empty; public AcmeSmartQueryString() {} [BasePageFramework.Attributes.SmartQueryStringKey("pn")] public string PageNumber { get{return _pgNum;} set{_pgNum = value;} } [BasePageFramework.Attributes.SmartQueryStringKey("ti")] public string TabIndex
  • 41. { get{return _tabIdx;} set{_tabIdx = value;} } [BasePageFramework.Attributes.SmartQueryStringKey("un")] public string UserName { get{return _userName;} set{_userName = value;} } } You can see that this class does not interact with it’s base members, this is the beauty of using the interface. Your do not need to worry about what is happening to translate the query string to and from this object because it all happens behind the scenes. You are now free to interact with this object as you need just like it’s another instance in your code. I left out one of the attributes so you can see what happens on the receiving end when the next page request is filled. You will see the whole name for the property. It just has state and that’s it! That’s the beauty of it, our developers using this framework do not need to know what is happening under the hood, they just have to interact with this instance and they are good to go… Keep in mind that the smart query string although maps properties to our smart query string, you can only use strings with it (hence the name) I am looking into ways to allow you to add other types to the query string (Enumerations, other value types etc…). I will leave that matter as the subject of a future post. Here is our Acme Master Page that not only initializes the other Master Page components but also loads our AcmeSmartQueryString public class AcmeBasePage : BasePageFramework.SuperBasePage { private AcmeControls.LeftNav _leftNav = null; protected override void LoadBaseUITemplate(ref BasePageFramework.IBaseTemplate tmplt)
  • 42. { tmplt = (BasePageFramework.IBaseTemplate)LoadControl("~/Base/AcmeBaseTemplate.a scx"); } protected override void LoadTemplatePanels() { _leftNav = (AcmeControls.LeftNav)LoadControl("~/AcmeControls/LeftNav.ascx"); ((AcmeBaseTemplate)this.MainUITemplate).LeftNavigation.Controls.Add(_le ftNav); _leftNav.OnNavClick += new AcmeWidgets.AcmeControls.LeftNavEvent(_leftNav_OnNavClick); } protected override void InitializeConfiguration(ref BasePageFramework.Configuration.ConfigLoadingArgs configParams) { configParams.BasePageID = "AcmeBase"; } protected override void InitSmartQueryString(ref BasePageFramework.SmartState.ISmartQueryString isqs) { isqs = (BasePageFramework.SmartState.ISmartQueryString)new AcmeSmartState.AcmeSmartQueryString(); } protected override void InitSmartSession(ref BasePageFramework.SmartState.ISmartSession ises) { ises = (BasePageFramework.SmartState.ISmartSession)new AcmeSmartState.AcmeSmartSession(); }
  • 43. private void _leftNav_OnNavClick(object sender, AcmeWidgets.AcmeControls.LeftNavEventArg e) { switch(e.PageNumber) { case "1": this.SmartQueryString.SmartRedirect("Default.aspx"); break; case "2": this.SmartQueryString.SmartRedirect("PageTwo.aspx"); break; case "3": this.SmartQueryString.SmartRedirect("PageThree.aspx"); break; case "4": this.SmartQueryString.SmartRedirect("PageFour.aspx"); break; default: this.SmartQueryString.SmartRedirect("Default.aspx"); break; } } } You can see in our event handler in the above page that we are using the SmartQueryString of the current instance (initialized in our base page when it gets requested) to do the redirection and keep our string elements intact, this not only makes it easier to manage the querystring, it also centralizes everything that happens in the querystring in one place making maintenance infinitely easier.
  • 44. On a side note, So I know what some of you are saying… But I HATE using reflection!! Why not use a formatter or typeconverter instead??!! To answer that question, you can write your own. I created interfaces for these objects so you can create your own smartquerystringbase class (along with the other smart state objects) – the master page base does not care – all it needs to keep a reference to are these interfaces so write your own if you must. Now for the other smart state objects things are not as automatic because we do not have the “Smart Redirect” to pass the data along to our code so it can be persisted. Therefore we need o tell the other SmartState Objects to persist themselves whenever we change something in them. These interfaces are fairly straight forward minus the fact that we have to set the state somehow, that is where “SetApplicationState” and “SetSessionState” come in. Pass these calls the appropriate state object and away you go! Here is our SmartSessionBase class that comes out of the box. [Serializable] public class SmartSessionBase : ISmartSession { // Fields [field: NonSerialized()] private HttpSessionState _session = null; private void ExtractSessionStateRefs() { foreach(PropertyInfo inf in this.GetType().GetProperties()) inf.SetValue(this,this._session[inf.Name],null); } void ISmartSession.AbandonSession() { this._session.Abandon(); } void ISmartSession.FlushSessionState() {
  • 45. this._session.Clear(); } void ISmartSession.PersistToSession() { foreach(PropertyInfo inf in this.GetType().GetProperties()) { this._session[inf.Name] = inf.GetValue(this,null); } } void ISmartSession.SetSessionState(HttpSessionState ses) { this._session = ses; this.ExtractSessionStateRefs(); } } Here is the AcmeSmartSessionBase implementation (I included the person object so you can see the class we are keeping in session state public class AcmeSmartSession : BasePageFramework.SmartState.SmartSessionBase { private Person _person = null; public Person SelectedPerson { get{return _person;}
  • 46. set{_person = value;} } } [Serializable()] public class Person { string _name = string.Empty; int _age = 0; int _weight = 0; public string Name { get{return _name;} set{_name = value;} } public int Age { get{return _age;} set{_age = value;} } public int Weight { get{return _weight;} set{_weight = value;} } } Using the application state is similar except that we lock the instance before we mess with it. Here is the code for our default SmartApplicationStateBase (not too much different from the SmartSessionBase aside from the thread sync stuff).
  • 47. public class SmartApplicationBase : ISmartApplication { [field: NonSerialized()] private HttpApplicationState _appState = null; #region ISmartApplication Members public void FlushAppState() { CheckInit(); _appState.Clear(); } public void PersistToAppState() { CheckInit(); _appState.Lock(); //update the derived smartapplication object to the application state... foreach(PropertyInfo inf in this.GetType().GetProperties()) this._appState[inf.Name] = inf.GetValue(this,null); _appState.UnLock(); } public void SetApplicationState(System.Web.HttpApplicationState state)
  • 48. { _appState = state; //extract the values out of application state so our smart application object //has everything it needs to be referenced in the page _appState.Lock(); foreach(PropertyInfo inf in this.GetType().GetProperties()) inf.SetValue(this,this._appState[inf.Name],null); _appState.UnLock(); } private void CheckInit() { if(_appState == null) throw new InvalidOperationException("The SmartApplicationState must be initialized before it is accessed"); } #endregion } Finally here is an example of how to use these objects (remember our master page already set these up for us so they are already to go when we need them). private void Button1_Click(object sender, System.EventArgs e) { Classes.AcmeClass cls = new Classes.AcmeClass("Chase",30); //create an object ((Master.AcmeSmartSession)this.SmartSession).Person = cls; //set it into our smartsession instance this.SmartSession.PersistToSession(); //update our current smart session to the session state
  • 49. AcmeSmartQueryString sqs = (AcmeSmartQueryString)this.SmartQueryString; sqs.Page = 2.ToString(); sqs.SmartRedirect("TestOne.aspx"); } And this is how you get to it when you need it… AcmeSmartSession sss = (AcmeSmartSession)this.SmartSession; if(sss.Person != null) sss.Person.Name = "Bob"; I am not going to post anything about how to use the Application State stuff since it works the exact same way as the SmartSession. That is the smart state objects in a nutshell – I am currently working on ways to make the persisting calls more efficient by adding an interface to check for a new / dirty / clean object. I will post the enhancements to this framework as they become available.