Morphfolia Tutorial: the ‘Bare Bones’ PageLayout

Morphfolia Tutorial: the ‘Bare Bones’ PageLayout

PageLayouts are a key extension point for Morphfolia; in this article we’ll introduce how to build a PageLayout by looking at the absolute bare minimum you have to do to get one un and running, line by line. The Morphological.Kudos project (part of the standard Morphfolia relase) is a library of PageLayouts, it serves both as an SDK and it is actually used ‘in production’ on morphological.geek.nz. My only regret with the current release of Kudos (v2.4.1.0) is that some of the code is a bit old and hasn’t been refactored to a high degree of polish; it certainly works – but it’s not as clear and clean as it could be. If you want the ‘definative’ guide to build PageLayouts then this article is the place to start.

First, we have the using statements and namespace declaration – nothing terribly complex about that. You can namespace your layouts anyway you like, but it is a good idea to think about this carefully in advance – any subsequent refactoring of the namespace will affect users who are actively using the PageLayout.

using System.Web.UI;
using System.Web.UI.WebControls;
using Morphfolia.Common.BaseClasses;
using Morphfolia.Common.Info;
using Morphfolia.PageLayoutAndSkinAssistant;
using Morphfolia.PageLayoutAndSkinAssistant.Attributes;

namespace Morphological.Kudos.Layouts
{

the class declaration is fairly standard, but there are a couple of things you need to do:

  1. Decorate the class with IsLayoutWebControl attribute (found in the Morphfolia.PageLayoutAndSkinAssistant.Attributes namespace).
  2. Inherit from Morphfolia.Common.BaseClasses.BasePageLayout
  3. Finally, its good practice when developing WebControls to include the System.Web.UI.INamingContainer interface.

Stricly speaking we don’t need to worry about INamingContainer as this PageLayout isn’t going to be functional (it won’t hold controls that need to do PostBacks, etc). Generally speaking the PageLayouts are used as templates for serving content not applications – but they can.

Decorating the class with IsLayoutWebControl means that the PageLayout will be available to users when adding and editing pages.

[IsLayoutWebControl]
public class BareBones : BasePageLayout, INamingContainer
{

This ‘BareBones’ example is going to be very basic; we’ll display the page title and content, only. In order to implement this we’ll declare a couple of controls to display the information – a Literal into which we’ll put the page title (formatted as a heading), and a Panel to hold whatever content is assigned to the page. Obviously you can put whatever you want in here – the specific controls used are relevant only to your PageLayout and aren’t required specifically to get the PageLayout working.

private Literal pageTitle;
private Panel contentPanel;

This next property is required by the base class (BasePageLayout). It allows callers to pass in WebControls for inclusion in the PageLayout, for example a site map, search results or tag-cloud. How you implement the property is up to you, but this is what I would normally do.

private Morphfolia.Common.WebControlCollection childControls;
public override Morphfolia.Common.WebControlCollection ChildControls
{
    get{
        EnsureChildControls();
        if( childControls == null )
        {
            childControls = new Morphfolia.Common.WebControlCollection();
        }
        return childControls;
    }
    set{
        childControls = value;
    }
}

Next we have the contructors: the standard arguement-less one, and one that can take a WebControl. You can dispense with the constructors if you wish, although you will need them both if you want to pass a WebControl straight in.

public BareBones()
{
}

public BareBones( WebControl childWebControl )
{
    ChildControls.Add( childWebControl );
}

One of the main reasons the PageLayouts exist is to support custom-properties – to enable end-users to be able to configure their pages how they want them. Here’s an example CustomProperty – in this case it allows users to set a width, which will control how wide the content is.

Currently the admin UI allows any text-based input, however, you can type custom-properties any way you like – all you need to do is validate the user input (which we’ll cover soon).

A CustomProperty is any ‘normal’ class property that is decorated with the IsCustomProperty attribute, it can be called whatever you like (subject to normal language restrictions). In addition to the IsCustomProperty attribute are several other attributes that provide metadata to the system, which drives the UI provided to the user when they work with the PageLayout whilst setting up a page.

What these additional attributes are, and what they do, should be self evident. All the attributes you see here take a string arguement, which is simply the value you want to assign (e.g: PropertyFriendlyName = “Content Width”). The Morphfolia.PageLayoutAndSkinAssistant.Attributes namespace provides a couple of classes (such as SuggestedUsageNotes and Descriptions) which contain pre-defined messages for some of the CustomProperties commonly used.

I’ve used plain text in this example, but you’re better off defining some constant values for consistency, especially if you’re going to develop a comprehensive PageLayout library.

private Unit overalWidth = Unit.Pixel(650);
[IsCustomProperty,
PropertyFriendlyName("Content Width"),
PropertyDescription("Use this property to set the width of the content area."),
PropertySuggestedUsage("Fixed width in pixels (e.g: 220px) or a percentage (e.g: 50%).")]
public Unit OveralWidth
{
    get{ return overalWidth; }
    set{ overalWidth = value; }
}

Defining content-containers (places for the user to assign content to the page) is pretty much identical to custom-properties: a ‘normal’ property decorated with the appropriate attributes, and it can be called whatever you like (subject to normal language restrictions). There are three attributes you can use, but only IsContentContainer is required. ContentContainerDescription allows you to describe what the content container is for (main content, navigation, footer, and so on) and ContentContainerColour allows you to color code the content containers – useful if you have a few. When the content container is displayed in the edit page UI (for users to assign content) the background of the content container is colored as per this setting (otherwise it’s a pale blue).

private string userContent = " ";
[IsContentContainer,
ContentContainerDescription("Main page content."),
ContentContainerColour("#ddeeff")]
public string UserContent
{
    get{ return userContent; }
    set{ userContent = value; }
}

If you provide a ‘Current Layout Icon’ you can indicate to the user what the selected PageLayout will look like and give visual clues as to how the content will be layed out; this icon is simply an 100 pixel square JPG which is optional – but strongly recommended. Here are some example PageLayout icons. The name of the file is based on the full type name and assembly name, syntax: “[type name], [assembly name].jpg” e.g: “Morphological.Kudos.Layouts.QuadPageLayout, Morphological.Kudos.jpg”

A simple PageLayout implemented as a 3 column table.
A PageLayout consisting of a table with 2 rows and 2 columns.
The PageLayout for a Blog: it has one content container that sits under the main blog area.
A space for content under a picture with properties.
A table with 10 content areas, all color coded for ease of content assignment in the admin section.

The FormTemplatePresenterType property defines the FormTemplate provider to be used by the PageLayout. This property is not required by the PageLayout base class but it is needed if you want to display FormTemplate structured content.

FormTemplate providers deserve documentation of their own (which will be forth coming), in a nutshell: FormTemplate Providers allow you to display structured data – as formatted by the provider, this is useful for lists or collections of things. I use a FormTemplate Provider to render items on my Product Backlog.  Normally content is input and stored as raw HTML (via the WYSIWYG editor), but you can also input structured content via a form; the user input form is generated automatically based on a simple XML file – you can have as many of these as you like.  All the XML file has to do is confrom to the simple FormTemplate schema.

When a user enters content via a FormTemplate the resulting is content is stored as XML; then when the page is served it is presented (i.e: formatted) via the specified FormTemplate Provider rather than being simply inserted into the output stream as raw HTML.

private string formTemplatePresenterType 
    = "Morphological.Kudos.FormTemplatePresenters.LivewireProblem, Morphological.Kudos";
public override string FormTemplatePresenterType
{
  get
  {
      return formTemplatePresenterType;
  }
  set
  {
      formTemplatePresenterType = value;
  }
}

Anyone who has built WebControls will be familiar with the CreateChildControls method – it’s where the majority of the WebControl is usually constructed. We don’t have to do anything specific here as far as a PageLayout is concerned, but it is the best place to start constructing the WebControl. As you can see – this example doesn’t require much work, we just need to instatiate a Literal for the page title and a Panel for the content.

protected override void CreateChildControls()
{
    pageTitle = new Literal();
    Controls.Add( pageTitle );

    contentPanel = new Panel();
    Controls.Add( contentPanel );
}

InitializeContent is a method we have to implement for a PageLayout to work, it’s where the PageLayout receives the content to be displayed and inserts it into the correct place in the PageLayout. In this example the PageLayout has only one content container so teh work we do here is fairly trivial, however, you could have multiple content containers to assign content to and you could also incorporate additional logic regarding the location and presentation of it if you wanted to.

public override void InitializeContent()
{
    EnsureChildControls();

    string propertyType;
    int temp;

    if (CustomProperties != null)
    {
        for (int i = 0; i < CustomProperties.Count; i++)
        {
            propertyType = CustomProperties[i].PropertyType.PropertyTypeIdentifier;

            if (propertyType.Equals(
    Morphfolia.Common.ControlPropertyTypeConstants.PropertyTypeIdentifiers.CONT))
            {
                temp = GetContentInfoIndexById(int.Parse(CustomProperties[i].PropertyValue));
                if (temp != Morphfolia.Common.Constants.SystemTypeValues.NullInt)
                {
                    AddContentToContentContainer(tdContentContainer,
                        Page.ContentItems[temp].ContentEntryFilter,
                        Page.ContentItems[temp].Content);
                }
            }
        }
    }
}

Let’s review the important statements. EnsureChildControls() ensures that the CreateChildControls method has been called. In this implementation we are going to insert content into the anticipated control structure of the PageLayout – if it’s not there the process will fail. You could use the InitializeContent method to get the content only – and not affect the control structure then you don’t need to call EnsureChildControls, but you’ll need to do this eventually and this is generally the right place to do it.

if (CustomProperties != null) simply ensures that we have some information worth continuing with.

propertyType = CustomProperties[i].PropertyType.PropertyTypeIdentifier; is where we grab the Property Type of the CustomProperty so that we can ensure that only ‘content’ is added; that check is the next line of code – the if statement:

if (propertyType.Equals(Morphfolia.Common.ControlPropertyTypeConstants.PropertyTypeIdentifiers.CONT))

The PropertyTypeIdentifiers class has constants that help keep the types of CustomProperties consistently defined.

All the content for a page is provied as a single collection (a ContentInfoCollection object), the problem is that this content by itself has no knowledge of where in the page it needs to go – because pages and content are loosely-coupled; the way they are coupled is via a collection of CustomProperties which includes not only all the content / content container ‘bindings’ but also all other CustomProperties as well.

The GetContentInfoIndexById method helps us arrange the content for the page correctly. Given the id of a content item (which is stored in the CustomProperty collection items as the PropertyValue property) the GetContentInfoIndexById method will scan the associated content collection (the underlying ContentInfoCollection object) and get the matching content item.

The code temp = GetContentInfoIndexById(int.Parse(CustomProperties[i].PropertyValue)); ensures that the content item was found, as we only want to add content we know should be there. If the result is equal to Morphfolia.Common.Constants.SystemTypeValues.NullInt we know that the content item we were looking for is not present in the ContentInfoCollection – e.g: the ContentInfoCollection and CustomPropertyCollection are out of sync.

Finally we call the AddContentToContentContainer method which performs the actual job of inserting the content where we want it.

AddContentToContentContainer takes three arguements:

  • contentContainer – the control that we want to append the content to.
  • contentEntryFilter – the content entry filter specifies how the content was entered and how it should be presented – see FormTemplates.
  • content – the content to add.

As mentioned earlier, content can be input as raw HTML or via what’s known as a FormTemplate, in which case the content is structered as XML. The AddContentToContentContainer method will append the raw HTML to the target control – or if the content is XML it will use a FormTemplate provider to present the content appropriately and append it to the target control as a WebControl.

The SetCustomProperties method is virtually identical in general purpose to the InitializeContent method – except that it deals with all the CustomProerties that aren’t content bindings.

The private CustomProperties field holds the internally used CustomPropertyInstanceInfoCollection which holds all the CustomProperties passed into the PageLayout. You don’t have to have this field but it’s a convenient way of working with the CustomProperties passed in.

Just as with the InitializeContent method, SetCustomProperties is usually used to accept the CustomPropertyInstanceInfoCollection passed in and use the contained values to ‘configure’ the PageLayout.

The CustomProperties passed in will typically contain values applicable to formatting (widths, colors, alignments and so on) but it can contain pretty much anything you want.

As with the InitializeContent method we call EnsureChildControls to ensure the expected control structure in instansiated.

Once we’ve established that the customProperties are worht working with we get to the heart of the method; first we get hold of the propertyKey and propertyValue. I then typically have a switch statement that evaluates the propertyKey, and for each propertyKey I want to handle I have a branch of code that uses the propertyValue that comes with the propertyKey. In this example I’m only expecting a CustomProperty that sets the ‘OveralWidth’.

All propertyKeys and propertyValues are strings, but I make use of constants for any properties which get common use – to encourage consistencey, hence: Constants.CommonPropertyKeys.OveralWidth.

As the CustomProperties sub-system only deals with strings – but you typcially code aganist specifically typed properties you need to do some validation and conversion; in this example I’m taking a string value which I expect to be able to convert into a unit (to set the ‘OveralWidth’), and if it fails I set a default value – defensive programming.

private CustomPropertyInstanceInfoCollection CustomProperties = null;

public override void SetCustomProperties(
    CustomPropertyInstanceInfoCollection customProperties)
{
    EnsureChildControls();

    CustomProperties = customProperties;
    string propertyKey;
    string propertyValue;

    if (CustomProperties != null)
    {
        for (int i = 0; i < CustomProperties.Count; i++)
        {
            propertyKey = CustomProperties[i].PropertyKey;
            propertyValue = CustomProperties[i].PropertyValue;

            switch (propertyKey)
            {
                case Constants.CommonPropertyKeys.OveralWidth:
                if (!propertyValue.Equals(string.Empty))
                {
                    try
                    {
                        OveralWidth = Unit.Parse(propertyValue);
                    }
                    catch
                    {
                        OveralWidth = Unit.Pixel(650);
                    }
                }
                break;
            }
        }
    }
}

The final step is to implement the RenderContents method, this is where our WebControl / pageLayout is fianlly committed to the output stream. If you want to do any final adjustments – this method is your last chance.

The EnsureChildControls method is called – just in case. We then check the Visible property – no point doing a bunch of work if the WebControl is not going to be visible (this probably won’t matter when serving the WebControl from an HTTP Handler – but it’s good practice as you may decide to use the WebControl elsewhere). You haven’t forgotten, of course, that a Pagelayout is bascially a WebControl, have you.

The ChildControls property is not on the critical path when developing a PageLayout – but it’s very handy. The BasePageLayout class provides the ChildControls property so that callers can add WebControls to the PageLayouts; as an implementer your only real choice is what to do with them. You can ignore them completely if you wish, or far more usefully you can put them somewher in your PageLayout. You could even be ultra clever and write some logic that put different controls in different places – assuming you knew what to expect.

For this example we’re just appending any controls passed in to the sole content container. Real life examples of this property in action are where Morphfolia adds the SiteMap WebControl, Search Results, Tag-Cloud and WordIndexListPresenter WebControls. See the Morphological.Kudos.Layouts.QuadPageLayout PageLayout as another example.

The very last thing we do in this method is commit the WebControl to the output stream, by calling the RenderContents mehtod, however, just before we do that we ‘connect the final set of dots’.

The pageTitle Literal is finally given it’s value: the title of the page (wrapped up in an HTML <H1> element), courtesy of the PageLayouts reference to the current pages’ underlying implementation of the Morphfolia.Common.Interfaces.IPage interface. This is basically an object that gives us access to the basic properties of the ‘page’ (Title, PageID and so on).

Then we set the width of the contentPanel content container to the value stored in the OveralWidth variable. As I hope I made sufficiently clear earlier – we can set the values against the controls (contentPanel.Width = OveralWidth) either here or back in SetCustomProperties where we processed the CustomProperties initially.

protected override void RenderContents(HtmlTextWriter writer)
{
    EnsureChildControls();

    if( Visible )
    {
        if( ChildControls.Count > 0 )
        {
            for(int c = 0; c < ChildControls.Count; c++)
            {
                tdContentContainer.Controls.AddAt(c, ChildControls[c]);
            }
        }

        pageTitle.Text = string.Format("<h1>{0}</h1>", base.Page.Title);

        contentPanel.Width = OveralWidth;

        base.RenderContents (writer);
    }
}

Morphfolia: Integration Options

At a high-level there are about 10 ways you can integrate with Morphfolia; this article gives an overview of these options.  All of the integration options here are possible without having to modify Morphfolia itself (with the exception of configuration).

Here’s a diagram for context (detailed information to follow beneath).  In the center are the main Morphfolia packages and classes that integrators will probably want (or need) to work with – depending on which approach you want to take.  The blue packages and classes on the sides represent code you might write yourself; with association lines showing the main integration relationships.  (Note the word ‘package’ comes from UML – where it refers to a logic group of items, in this article package also often refers to an assembly).

Click image for a full-size copy (1516 x 1292 pixels, 272Kb)

Click image for a full-size copy (1516 x 1292 pixels, 272Kb)

Let’s go through the 10 options in more depth.

(1) Develop your own website (ASP.NET / ASP.NET MVC) and leverage Morphfolias APIs

The basic idea is to use Morphfolia as a base – rather than starting with a new blank ASP.NET Web Application project; however, you can still use a new blank project if that’s easier for your situation and then leverage the Morphfolia packages and classes that are useful to you.

As shown on the diagram, the Publishing System is possibly what you’re after – although you can leverage pretty much anything (Business Logic, WebControls, Data Access and so on).

(2) Use the Web.Config to route requests to your HttpHandlers, which can also leverage Morpfolia APIs

A lot of flexibility comes from the web.config: direct Http Requests to the mechanism best suited to handling them: could be Microsofts out-of-the-box ASP.NET WebForm handler (System.Web.UI.PageHandlerFactory), Morphfolia’s default HttpHandler or one you’ve developed yourself.

Your HttpHandler can then leverage various packages and classes in Morphfolia.  As per the diagram you’ll probably want to use the Publishing System but there’s nothing stopping you using the Business Logic (Http Logging, the CustomPropertyHelper, etc) and other packages.

(3) WebForms (and other things) can use the WebFormHelper

The WebFormHelper was specifically designed to work with WebForms, but may also be useful with any MasterPages and UserControls that you write yourself.  For more info on how you use the WebFormHelper with WebForms see: Using in an ASP.NET WebForm.

In essence the WebFormHelper can be used in “code behind” and “in-line” as per options 4 and 5 below, the thing that makes it worthy as an integration option in it’s own right is that it’s specifically designed for use with WebForms – allowing you to easily perform common Morphfolia / WebForm integration tasks.

(4) Your MasterPage, WebForm or UserControl views call functionality using in-line code

For example, here’s an example of using the WebForm helper “inline” in a .aspx page which uses a master page; you could do the same thing in the MasterPage itself, or a UserControl.  Just be careful where you call the HttpLogger (don’t call it more than once per pageload).

<%@Page 
Language=”C#” 
MasterPageFile=”~/SimpleTemplatingTestMasterPage.master”
AutoEventWireup=”true”
CodeFile=”SimpleTemplatingTestWebForm.aspx.cs”
Inherits=”SimpleTemplatingTestWebForm”
Title=”Untitled Page” 
%><asp:Content ID=”Content1″ ContentPlaceHolderID=”head” Runat=”Server”></asp:Content><asp:Content ID=”Content2″ ContentPlaceHolderID=”ContentPlaceHolder1″ Runat=”Server”><%  Morphfolia.PublishingSystem.WebFormHelper webFormHelper = new Morphfolia.PublishingSystem.WebFormHelper();lblProperties.Text = webFormHelper.Page.Description;%>

<p><asp:Label ID=”lblProperties” runat=”server”></asp:Label>
</
p>

<p style=”border: dashed 2px red”>CustomProperties Count: <%= webFormHelper.CustomProperties.Count.ToString() %></p>

</asp:Content>

(5) Your code behind or WebControls call functionality in the Publishing System

You can also (as you’d expect) use the Morphfolia API’s from the code behind in MasterPages (as shown here), WebForms, UserControls and in WebControls.

public partial class SimpleTemplatingTestMasterPage : System.Web.UI.MasterPage
{
protected void Page_Load(object sender, EventArgse)
{
Morphfolia.PublishingSystem.WebFormHelper.LogRequest(HttpContext.Current);
Morphfolia.PublishingSystem.WebFormHelper webFormHelper = new Morphfolia.PublishingSystem.WebFormHelper();
litPageHeading.Text = webFormHelper.Page.Title;
}
}

(6) Your code behind calls Morphfolia Business Logic

The Publishing System (including the WebFormHelper) exist to bring consistency to your site or application – by allowing you to skin WebForms and ‘content’ from Morphfolia using the same underlying mechanisms (PageLayouts and so on).

Another option available to you is to go straight to the Business Logic and use the APIs in a more isolated manner (rather than using the ‘normal’ Morphfolia website (with it’s publishing section and so on).  Although it depends what you’re aiming to do you might want to do this to leverage:

  • The CustomPropertyHelper or StaticCustomPropertyHelper
  • The ContentIndexer or SearchEngine
  • Or maybe just the Constants

Using the Business Logic APIs you can create WebForms that go straight to whatever core functionality you want.

(7) Serve up your WebControls via HttpHandlers

The default HttpHandler provide with Morphfolia is responsible for serving the majority of user facing pages, including both administrator defined content and the ‘special’ pages (Search Results, Site Map and Site Index); all of these special pages simply creat an instance of a WebControl and write it to the output stream.

You can do this by modifiying the default HttpHandler or writing your own (use the default one as a starting point).  Here’s how the default HttpHandler does it (in this case to serve out a TagCloud, which the current pageLayout will place where ever it’s been told to); the default Httphandler uses a simple switch statement that checks the PageName and instansiates a particular WebControl as required:

case“tags”:Morphfolia.Business.ContentIndexer contentIndexer = new Morphfolia.Business.ContentIndexer();

Morphfolia.WebControls.TagCloud tagCloud = new Morphfolia.WebControls.TagCloud();

tagCloud.NumberOfTagDivisons = 15;
tagCloud.MinimumOccurranceThreshold = 20;
tagCloud.Words = contentIndexer.GetWordsForTagCloud(tagCloud.MinimumOccurranceThreshold);
tagCloud.NavigateUrl = string.Format(“{0}/SearchResults.aspx”, WebUtilities.VirtualApplicationRoot());

tagCloud.NavigateUrlQueryStringKey = RequestKeys.SearchCriteriaIndentifier;
pageLayout.ChildControls.Add(tagCloud);
break;

And in case you’re wondering, this is (basically) how you get an HttpHandler to serve up a WebControl:

public class DefaultHandler : IHttpHandler
{
StringWriter sw = new StringWriter();
HtmlTextWriter writer = new HtmlTextWriter(sw);
Morphfolia.WebControls.WordIndexListPresenter wordIndexListPresenter = new Morphfolia.WebControls.WordIndexListPresenter();
wordIndexListPresenter.RenderControl(writer);
httpContext.Response.Write(sw.ToString());
}

(8) Your WebControls can leverage morphfolias Business Logic APIs

This is bascially the same as Option 6, except that by using WebControls your functionality can be easily re-used in multiple systems, and can also be served out through HttpHandlers (option 7).

(9) Your Business Logic calls the Morphfolia Business Logic

You can also work at the Business Logic level; develop a complex application of your own that uses some of the logic in Morphfolia.  You can, of course, also call the logging methods provided in the Buisness Logic – or the under-lying methods in the Common assembly; this will help isolate changes in the logging providers (the MS Enterprise Libraries).

(10) You Business Logic calls the Morphfolia Data Provider via its Interface

If you like the MS SQL DataProvider you can call it directly (preferably via the interfaces located in the IDataProvider assembly)

Notes

  • There are a variety of Helper classes in the Publishing System, Business Logic and Common assembilies; they’ve mostly been written with the intent of being used by external code.
  • The one major integration point missing is a WebService facade.  There currently aren’t any plans to develop a WebService facade in the immediate future – although this would change if enough people contact me asking for it :)

Using in an ASP.NET WebForm

Intgrating Morphfolia with “standard” ASP.NET pages is easy-peasy; let’s take a look…

Here’s (part of) the mark-up for a typical ASP.NET page:

<body>

  <asp:PlaceHolder ID="PageHeader" runat="server"></asp:PlaceHolder>

  <form id="form1" runat="server">

  <asp:PlaceHolder ID="PageContent" runat="server"></asp:PlaceHolder>

  </form>

  <asp:PlaceHolder ID="PageFooter" runat="server"></asp:PlaceHolder>

</body>

…And here is the code-behind:

protected void Page_Load(object sender, EventArgs e)
    {
        Morphfolia.PublishingSystem.WebFormHelper.LogRequest(System.Web.HttpContext.Current);
        Morphfolia.PublishingSystem.WebFormHelper webFormHelper = new Morphfolia.PublishingSystem.WebFormHelper();

        webFormHelper.AmendHTMLHead(Page);

        PageHeader.Controls.Add(webFormHelper.CreateHeader());
        PageContent.Controls.Add(webFormHelper.ContentForPage());
        PageFooter.Controls.Add(webFormHelper.CreateFooter());
    }

The PageHeader and PageFooter PlaceHolders will be where the skin gets embedded (bascially, its exactly the same way the HTTP Handler wraps it around the specificed PageLayout (WebControl) when Morphfolia serves content out normally).

The PageHeader is outside the FORM tag as the header (part of the skin) that I’m using has it’s own FORM tag.

Finally, the content for the page (as added by the user in the admin area) is piped into the PageContent PlaceHolder; this is done by adding a page as you would (through the admin area / CMS functionality) but then including the actual ASP.NET page/files and altering the config so that the URL is handled by the normal ASP.NET engine and not the Morphfolia HTTP Handler.

This is a simple example, and you can get far more complex if you want.