Want to follow this site? Here's the RSS feed.

Minima 3.1 Released

Monday, October 20, 2008

I've always thought that one of the best ways to learn or teach a series of technologies is to create either a photo gallery, some forum software, or a blog engine.  Thus to aide in teaching various areas of .NET (and to have full control over my own blog), I created Minima v1.  Minima v2 came on the scene adding many new features and showing how LINQ can help make your DAL shine.  Then, Minima v3 showed up and demonstrated an enormous load of technologies as well as demonstrating proper architectural principles.

Well, Minima 3.1 is an update to Minima 3.0 and it's still here to help people see various technologies in action.  However, while Minima 3.1 adds various features to the Minima 3.1 base, its primary important to note that it's also the first major Themelia 2.0 application (Minima 3.0 was built on Themelia 1.x).  As such, not only is it a prime example of many technologies ranging from WCF to LINQ to ASP.NET controls to custom configuration, it's also a good way to see how Themelia provides a component model to the web.  In fact, Minima 3.1 is technically a Themelia 2.0 plug-in.

Here's a quick array of new blog features:

  • It's built on Themelia 2.0.  I've already said this, but it's worth mentioning again.  This isn't a classic ASP.NET application.  It's the first Themelia 2.0 application.
  • Minima automatically creates indexes (table of contents) to allow quick viewing of your site
  • Images are now stored in SQL Server as a varbinary(max) instead of as a file.
  • Themelia CodeParsers are used to automatically turn codes like {Minima{BlogEntry{3324c8df-4d49-4d4a-9878-1e88350943b6}}} into a link to a link entry, {Minima{BlogEntry{3324c8df-4d49-4d4a-9878-1e88350943b6|Click here for stuff}}} into a renamed blog entry and {Minima{AmazonAffiliate{0875526438}}} into a Amazon.com product link with your configured (see below) affiliate ID.
  • Minima now has its own full custom configuration.  Here's an example:
<minima.blog entriesToShow="7" domain="http://www.tempuri.org/">
  <service>
    <authentication defaultUserName="jdoe@tempuri.org" defaultPassword="blogpassword"/>
    <endpoint author="AuthorServiceWs2007HttpBinding" blog="BlogServiceWs2007HttpBinding" comment="CommentServiceWs2007HttpBinding" image="ImageServiceWs2007HttpBinding" label="LabelServiceWs2007HttpBinding" />
  </service>
  <suffix index="Year Review" archive="Blog Posts" label="Label Contents" />
  <display linkAuthorsToEmail="false" blankMessage="There are no entries in this view." />
  <comment subject="New Comment on Blog" />
  <codeParsers>
    <add name="AmazonAffiliate" value="httpwwwnetfxh-20" />
  </codeParsers>
</minima.blog>
  • In addition to the normal MinimaComponent (the Themelia component which renders the blog with full interactivity), you may also use the MinimaProxyComponent to view single entries.  For example, you just as a BlogEntryProxy component to a web form and set either the blog entry guid or the blog guid plus the link, then your blog entry will show.  I built this feature in to allow Minima to be used for more than just blogging, it's a content stream.  With this feature I can keep every single page of a web site inside of Minima and no one will ever know.  There's also the MinimaViewerComponent which renders a read-only blog.  This means no rsd.xml, no site map, no commenting, no editing, just viewing.  In fact, the Themelia web site uses this component to render all its documentation.
  • There is also support for adding blog post footers.  Minima 3.1 ships with a FeedBurner footer to provide the standard FeedBurner footer.  See the "implementing" section below for more info.

As a Training Tool

Minima is often used as a training tool for introductory, intermediate, and expert-level .NET.

Minima 2.0 could be used as a training tool for ASP.NET, CSS theming, proper use of global.asax, integrating with Windows Live Writer, framework design guidelines, HttpModules, HttpHandlers, HttpHandlerFactories, LINQ, type organization, proper-SQL Server table design and naming scheme, XML serialization, and XML-RPC.NET usage.

Minima 3.1 can be used as a training tool for the same concepts and technologies as Minima 2.0 as well as SOA principles, custom WCF service host factories, custom WCF behaviors, WCF username authentication, custom WCF declarative operation-level security, WCF exception shielding and fault management, custom WCF message header usage, WCF type organization, WCF-LINQ DTO transformation, enhanced WCF clients, using WCF sessions for Captcha verification, SQL Server 2005 schema security, XmlWriter usage, ASP.NET programmatic user control usage, custom configuration sections, WCF JavaScript clients, ASP.NET control JavaScript registration, JavaScript namespaces, WCF JSON services, WCF RSS services, ASP.NET templated databinding, and ASP.NET control componentization.

Architecture

Probably the most important thing to learn from Minima is architecture.  Minima is built to provide great flexibility.  However, that's not for the faint of heart.  I heard one non-architect and obvious newbie say that it was "over architected".  According to this person, apparently, adding security to your WCF services to protect you private information is "over architecting" something (not to mention the fact that WCF enforces security for username authentication).

In any case, Minima is split into two parts: the service and the web site.  I use Minima many places, but for all my blogs (or, more accurately, content streams) I have a single centralized, well-protected service set.  All my internal web sites access this central location via the WCF NetNamedPipeBinding.

Implementing

Minima is NOT your every day blog engine.  If your company needs a blog engine for various people on the team, get community server.  Minima isn't for you.  Minima allows you to plop a blog into any existing web site.  For example, if you have an existing web site, just install Themelia (remember, Minima is a Themelia plugin), create a new Themelia web domain, and register Minima into that web domain as follows:

<themelia.web>
  <webDomains>
    <add>
      <components>
        <add key="Minima" type="Minima.Web.Routing.MinimaComponent, Minima.Web">
          <parameters>
            <add name="page" value="~/Page_/Blog/Root.aspx" />
            <add name="blogGuid" value="19277C41-7E4D-4AE0-A196-25F45AC48762" />
          </parameters>
        </add>
      </components>
    </add>>
  </webDomains>
</themelia.web>

Now, on that Root.aspx page, just add a simple Minima.Web.Controls.MinimaBlog control.  Your blog immediately starts rendering.  Not only that, commenting is automatically supported.  Furthermore, you have a site map, a Windows Live Writer (MetaWeblog API) endpoint, a rsd.xml file, and a wlwmanifest.xml file.  All that just dropping a control on to a web site without configuring anything in that page.  Of course, you can configure things if you want and you can add more control to the page as well.  Perhaps you want a label list, an archive list, or a recent entry list.  Just add the appropriate control to the web form.  In fact, the same Minima binaries that you will compile with the source is used on each of my web sites with absolutely no changes; they are all just a single control, yet look nothing alike.

Personally, I don't like to add a lot of controls to my web forms.  Thus, I normally add a place holder control and then add my controls to that place holder.  There more here's a snippet from my blog's web form (my entire blog has only one page):

phLabelList.Controls.Add(new Minima.Web.Controls.LabelList { Heading = "Label Cloud", ShowHeading = true, TemplateType = typeof(Minima.Web.Controls.LabelListControlTemplateFactory.SizedTemplate) });
phArchivedEntryList.Controls.Add(new Minima.Web.Controls.ArchivedEntryList { ShowEntryCount = false });
phRecentEntryList.Controls.Add(new Minima.Web.Controls.RecentEntryList());
phMinimaBlog.Controls.Add(new Minima.Web.Controls.MinimaBlog
{
    ShowAuthorSeries = false,
    PostFooterTypeInfo = Themelia.Activation.TypeInfo.GetInfo(Minima.Web.Controls.FeedBurnerPostFooter.Type, "http://feeds.feedburner.com/~s/FXHarmonics"),
    ClosedCommentText = String.Empty,
    DisabledCommentText = String.Empty
});

There's nothing here that you can't do as well.  Most everything there is self explanatory too.  However, notice the post footer type.  By setting this type, Minima knows to render the feed burner post footer at the end of each entry.

Thus, with a simple configuration and a drop of a control, you can add a blog anywhere.  Or, in the case of the Themelia web site, you can add a content stream anywhere.

Here's a snippet from the configuration for the Themelia web site:

<add name="framework" path="framework" defaultPage="/Sequence_/Home.aspx" acceptMissingTrailingSlash="true">
  <components>
    <add key="Minima" type="Minima.Web.Routing.MinimaViewerComponent, Minima.Web">
      <parameters>
        <add name="blogGuid" value="19277C41-7E4D-4AE0-A196-25F45AC48762" />
      </parameters>
    </add>
  </components>
</add>

By looking at the Themelia web site, you can see that on the Themelia web site, Minima isn't being used as a blog engine, but as a content stream.  Go walk around the documentation of http://themelia.netfxharmonics.com/framework/docs.  I didn't make a bunch of pages, all I did was drop in that component and throw a Minima.Web.Controls.BlogViewer control on the page and BAM I have an entire documentation system already built based upon various entries from my blog.

As a side note, if you look on my blog, you will see each of the Themelia blog entries have a list of links, but the same thing in the Themelia documentation does not have the link list.  This is because I've set IgnoreBlogEntryFooter to true on the BlogViewer control and thus telling Minima to remove all text after the special code.  Thus I can post the same entry in two places.

This isn't a marketing post on why you should use Minima.  If you want to use Minima, go ahead, you can contact me on my web site for help.  However, the point is to learn as much as you can about modern technology using Minima as an example.  It's not meant to be used in major web sites by just anyone at this point (though I use it in production in many places).  Having said that, the next version of Minima will be part of the Themelia suite and will have much more user support and formal documentation.

In conclusion, I say again (and again and again), you may use Minima for your personal training all you want.  That's why it's public. 

Links

(0 Comments)

Themelia Framework 2.0 Beta 4 Released

Thursday, October 16, 2008

Today again marks the point of another major milestone in the development of Themelia Framework 2.0 as I release beta 4.  This release includes many new concepts.  Of these, the major ones are WCF service aliasing, file aliasing, missing trailing slash handling, exception type filters for error processors, object factories, simplified XML configuration, regular expression alias patterning, and the inclusion of Themelia sequences.  There's already a bunch of lessons of using these features on the Themelia web site, so I won't explain any of them here.  However, I will give a quick summary of each below.  For more information on any feature, just check out the documentation on the Themelia web site.

  • A service alias (which requires IIS7 integrated mode) allows you to turn /Service_/Feed.svc/70C707BB-FC85-41ba-8802-30AF4F6AEF3F/10 into /feed.xml.
  • A file alias is essentially a way to alias a file to decouple its logical view (i.e. the path) from the physical file (in other words, it's a symbolic link).
  • Missing trailing slash handling, allows you to set a boolean to accept /framework as well as /framework/ when only /framework/ is installed (and it allows you to require the end slash).
  • You can now tell an error processor which exceptions that it is to handle.  If you want an error processor to only run when an OutOfMemoryException is thrown, just configure it that way.
  • Object factories create any type of object.  This is how, for example, the new exception filtered error processors model may allow you to type "ArgumentNullException" instead of the entire "System.ArgumentNullException, System" (with 172 exceptions ready for use in their abbreviated form-- with the ability for you to add more via object factories).
  • The XML configuration (web) is a lot more streamlined.  Now all processors go in <processors></processors> and all factories go in <factories></factories>.
  • Now you can do stuff regular expression aliasing to do stuff like mapping /product/([a-z0-9]+)/ to /Page_/Information/Product.aspx?sku=$1 and /service/([a-z0-9]+)/ to /Service_/$1.svc.  All captured data that looks like a traditional query string is actually captured in a new Themelia data state called "capture state".
  • Lastly (well, not last, but the last I'm going to mention here), Themelia sequences brings a proven web design pattern to the framework by allowing a centralize controller on a page to load various physically separated views in a workflow manner (e.g. "next", "previous", "first", "last").  Those one sentence explanations aren't nearly enough for anyone so you can checkout the full documentation on each of these by following the links in the above text.

These are just the main points.  There are a bunch of smaller additions, but they will be discussed further in the documentation.

In addition to the release of beta 4, Themelia now has its own home at http://themelia.netfxharmonics.com/.  This web site houses the binaries, the documentation, as well as various other things like a Themelia glossary and a quick reference section.  Because of this web site, Themelia will no longer be found on CodePlex and all future documentation will be posted there.

Themelia Samples are found at http://svn.netfxharmonics.com/Themelia/trunk/Samples/.  This is a web-based subversion repository and while it may be viewed in any browser, it requires TortoiseSVN to download.

Links

Fundamentals of Themelia - File Aliasing

Thursday, October 16, 2008

In addition to page aliases and service aliases, Themelia provides a way to alias single files.  This type of alias is actually rather similar to the concept of symbolic links in Unix-style operating systems.  The basic idea is that there may be one or more logical paths for the same physical resource.  This decoupling of your logical and physical world's allows you to rename a physical file without the rest of the world seeing broken link.

One example of how a file alias could be used can be seen in the major products around the world.  Say there was a major JavaScript framework in the world that publicly releases its single-file framework as ProductName.VersionNumber.js as in my-framework.1.0.4.js.  What happens when this framework becomes my-framework.1.0.5.js?  Now you have to chase your links all over the place to upload them to the new path (and hope the caching catches up sooner rather than later).  Fortunately, this isn't what these guys typically do.  Instead, the people who deploy the framework create an alias called my-framework.latest.js which points to the real file.  Now when the version changes, they only change the connection to that file in some type of internal resource (or in the case of Unix/Linux, change the symbolic link).  They don't have to ever touch the physical endpoint shown to the world.

You can do this same type of this using a Themelia file alias.  Here's an example:

<handlers>
  <add matchType="endsWith" name="FileAlias" text="/download/my-framework.latest.js" />
</handlers>

As with a page alias and a service alias, after the handler has been registered to watch a particular path based on a particular match type, an alias must be registered which is keyed to the handler by handler text.  As you can see, the target of the alias points to the true file.

<aliases>
  <add key="/download/my-framework.latest.js" target="/Resource_/Release/my-framework.1.0.4.js" extra="text/javascript" />
</aliases>

Content Type

Something that you will notice about this is that there's also another attribute, called extra.  This attribute is used differently by different alias types, but, in the case of a file alias, it sets the content type of the resource.  In this example, the physical resource will be streamed to the client as a text/javascript file.

This content type can be anything, but it needs to make sense for what you are doing.  For example, if you make an alias for a zip file, make sure you are sending the file as using application/zip.  When you want to alias a new type of file, be sure to do a quick Google search to see what the appropriate content type is for that particular file type.  One resource to get you started is http://www.webmaster-toolkit.com/mime-types.shtml.  This is a list of content types and how they map to common file extensions.

Initialization Parameters

Now as with page aliases, you can use initialization parameters to setup a file alias.  Here's an example:

<handlers>
  <add matchType="endsWith" name="FileAlias" text="/download/my-framework.latest.js">
    <parameters>
      <add name="target" value="/Resource_/Release/my-framework.1.0.4.js" />
      <add name="extra" value="text/javascript" />
    </parameters>
  </add>
</handlers>

This other type of syntax produces the exact same result, but does so in a more in-line manner.

Loose Coupling

Like with pages and services, one extremely important point about aliases relates to coupling.  If you have a publicly accessible resource whose logical path matches its physical path, you may find yourself in big trouble in the future.  The moment your physical resource moves or is renamed, all links, local and remote, must be changed.  Therefore, if you are versioning public files, perhaps you should think about using an alias from the start.

Links

Introduction to Themelia Sequences

Wednesday, October 15, 2008

Many times with working with a web form, you aren't actually working with a single logical unit.  Sometimes there are different steps to the process which your web forms represents.  Perhaps it's an account wizard, a checkout workflow, or just a contact page with an input and "thank you" page.  In each of these situations you aren't working with a simple web form, but a more complex sequence of pages.  The common solution for this type of situation is design pattern which splits each part of the sequence into its own page with a centralized controller moving the user around.

Themelia brings this abstract design pattern to the concrete world with a concept called a sequence.  This concept, though extremely simple, can seriously improve the manageability of your forms.  In fact, sequences aren't just for wizards, they can help organize any set of related pages.

Once you get used to sequences you may actually find yourself using them more than classic web forms.  Because of this, web site structure sample shown in the introduction to Themelia web site design shows how pages are the minority in Themelia web sites.  Where as sequences become the overall helper for various related pages, individual pages are left for simple single-view pages like a FAQ or an address list.

Creating a Sequence

To begin the discussion on sequences, look at the following sample:

image

Here we see three sequences: Checkout, PasswordReset, and Security.  For now, we are going to zoom out attention into the darkened region, namely, the PasswordReset sequence.

A sequence is created by using sequence page, containing a sequence controller, which contains a series of sequence views.  The sequence page holds the declaration of the different screens that will be shown, the controller simply houses the different views, and the view is the scenario-specific display.

Enough theory, let's go inside of the PasswordReset.aspx sequence:

<%@ Page CodeFile="PasswordReset.aspx.cs" Inherits="Sequence.PasswordReset" %>
<t:SequenceController SequenceName="PasswordReset" ID="Controller" runat="server">
    <t:SequenceView ID="Input" runat="server" />
    <t:SequenceView ID="Success" runat="server" />
</t:SequenceController>

This page represents what just about every sequence will look like: you have registered user controls and a sequence controller with an ID of "Controller".  In this controller there is a required attribute called SequenceName.  This is the name of the sequence and, therefore, also the name of the folder which contains the views.

This sequence has two SequenceView controls, each with an ID suffixed with "View".  Each view ID, minus the "View" suffix" matches to a user control of the same name under the folder declares by the SequenceName property.  For example, in this case, the view controls are "PasswordReset\Input.ascx" and "PasswordReset\Success.ascx".

That's a complete sequence.  Now let's move to the code behind of the sequence itself:

namespace Sequence
{
    public partial class ProblemReset : Themelia.Web.Controls.SequencePage
    {
        //- @SelectInitView -//
        public override String SelectInitView()
        {
            return "Input";
        }
    }
}

Sequence pages inherit from Themelia.Web.Controls.SequencePage and are require to override the SelectInitView method.  This method allows you to set the initial view based on whatever login you decide.  The following example is a contact sequence that should give you a better idea of what kind of logic we're talking about:

<t:SequenceController SequenceName="Contact" ID="Controller" runat="server">
    <t:SequenceView ID="ProblemInput" runat="server" />
    <t:SequenceView ID="ProblemSaved" runat="server" />
    <t:SequenceView ID="MessageInput" runat="server" />
    <t:SequenceView ID="MessageSent" runat="server" />
</t:SequenceController>

Here's the code behind:

namespace Sequence
{
    public partial class Contact : Themelia.Web.Controls.SequencePage
    {
        //- @SelectInitView -//
        public override String SelectInitView()
        {
            String firstUrlPart = Themelia.Web.Http.GetHttpPart(Themelia.Web.Http.Position.First);
            if (firstUrlPart == "suggestion" || firstUrlPart == "contact")
            {
                return "MessageInput";
            }
            else if (firstUrlPart == "problem")
            {
                return "ProblemInput";
            }
            //+
            return "MessageInput";
        }
    }
}

As you can see, when the sequence loads it checks to see if the first part of the URL (http://www.tempuri.org/thispart/) is "suggestion" or "contact".  If so, then the user wants to send a suggestion or contact someone.  However, this same sequence handles public Q/A as well.  Perhaps when an uncaught exception is thrown somewhere in the web site, the user is redirected here to describe what he or she was doing.  If the user is accessing the /problem/ path, then the sequence is told to load that.

The view that is loaded based upon the SelectInitView selection is the name of the view returned plus the suffix of "View.  Therefore, the possibilities in the above SelectInitView method are MessageInputView and ProblemInputView.  It's important to note that if an incorrect view name is returned, then no view will show.  So, if you want a view to show, then be sure to set a default ("MessageInput" in the above example).

Now, what's exactly inside the view?  As previously states, the view directly matches with a user control.  This user control must inherit from Themelia.Web.Controls.SequenceUserControl.  That's the only requirement.  Going back to the password reset sequence, here is the Input.ascx file:

<%@ Control AutoEventWireup="false" Language="C#" Inherits="Sequence.ResetPassword.Input" CodeFile="Input.ascx.cs" %>
<h1>Password Reset</h1>
<div class="contact-input">
    <strong><label for="<%=txtEmail.ClientID %>">E-mail Address</label></strong><br />
    <asp:TextBox ID="txtEmail" runat="server"></asp:TextBox><br />
    <br />
    <asp:Button ID="btnSubmit" runat="server" Text="Go to step 2"></asp:Button>
    <span class="error-message"><asp:Literal ID="litError" runat="server"></asp:Literal></span>
</div>

The code behind we have something like that looks like this:

//- #OnInit -//
protected override void OnInit(EventArgs e)
{
    btnSubmit.Click += new EventHandler(OnSubmit);
    //+
    base.OnInit(e);
}

//- $OnSubmit -//
private void OnSubmit(object sender, EventArgs e)
{
    litError.Text = String.Empty;
    //+
    Boolean success = LoginClient.RunCustomPasswordResetLogic(txtEmail.Text);
    if (success)
    {
        this.MoveToNextView();
    }
}

Just about everything about this is classic ASP.NET.  However, notice that when the password reset is successful, the control makes a call to MoveToNextView.  This is one of four different navigation methods to which both the sequence page and the sequence user control have access.  Here are the navigation methods:

  • MoveToFirstView
  • MoveToLastView
  • MoveToPreviousView
  • MoveToNextView

The function of each of them should be obvious.  In this case, when we know the password reset has been successful, we can simply move to the next view.  Therefore, the order of the sequence views are important to the success of sequence.

View Initialization

One extremely important note about sequences is that when you switch to a new view using one of the above methods, the OnViewInit method is called on the newly loaded sequence user control.  This is very important for wizard scenarios.  For example, say you are on view A and on this view you are asking for the user's first name, last name, and e-mail address.  If you needed any information on the second view, how does it get there?

The recommended solution for this is saving the information is HttpData and loading the information in OnViewInit on the next sequence user control.  For example, do this on view A:

//- $btnSubmit_Click -//
private void btnSubmit_Click(Object sender, EventArgs e)
{
    HttpData.SetScopedItem<String>("Source", "Email", txtEmail.Text);
    //+
    this.MoveToNextView();
}

Then on the next view, do this:

//- @OnViewInit - //
public override void OnViewInit()
{
    litEmailAddress.Text = Themelia.Web.HttpData.GetScopedItem<String>("Source", "Email");
}

Of course, the HttpData class also allows you to more than just strings.  If you needed to transfer a bunch of information, you could store that in HttpData as well.

Reusing User Controls

There's no reason to create a user control for a bunch of different things if they are actually the same.  Image that you had two purposes for an input and success view combo, but the differences in the two purposes requires only that there be a very minor difference between them.  For example, you are working with a Process sequence that has two pages: input and saved.  Further, you "process" you types of things, but the input content for each as well as the saved content for each are the same except for some textual differences (or perhaps a bit of logic setting a control as hidden, or whatever).  In this situation you can could a single input view and a single success view and simply register them multiple times using the ControlName attribute.  Here's an example:

<t:SequenceController SequenceName="Process" ID="Controller" runat="server">
    <t:SequenceView ID="Input" runat="server">
    <t:SequenceView ID="Saved" runat="server">
    <t:SequenceView ID="ComplexInput" ControlName="Input" runat="server">
    <t:SequenceView ID="ComplexSaved" ControlName="Saved" runat="server">
</t:SequenceController>

In this case, the "Process\Input.ascx" and "Process\Saved.ascx" user controls are each used twice.  The logic to figure out what's what would be inside of each control.  Say you had the following configuration:

<themelia.web>
  <webDomains>
    <add defaultPage="/Page_/Security/Home.aspx">
      <handlers>
        <add matchType="contains" name="PageAlias" text="/process/complex/" />
        <add matchType="contains" name="PageAlias" text="/process/single/" />
      </handlers>
      <aliases>
        <add key="/process/complex/" target="/Sequence_/Process.aspx" />
        <add key="/process/single/" target="/Sequence_/Contact.aspx" />
      </aliases>
    </add>
  </webDomains>
</themelia.web>

With this, you can easily do something like the following in Input.ascx or Saved.ascx to centralize your views:

//- #OnInit -//
protected override void OnInit(EventArgs e)
{
    if (Themelia.Web.Http.GetHttpPart(Themelia.Web.Http.Position.Second) == "single")
    {
        ddlMyComplexDropDownList.Visible = false;
        litMyComplexText = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.";
    }
    //+
    base.OnInit(e);
}

Note that you could also use the ControlName attribute for all the views.  This allows you to completely decouple your view names from your control names.

Setting the Page Title

Since a sequence consists of multiple views, each view could potentially require it's own page title.  Fortunately, setting the page title from a user control is as easy as setting the PageTitle property.  It's important to not that in the scope of the ASP.NET page life cycle, this title can be set as late as the OnPreRender method. Here's an example:

namespace Sequence.Contact
{
    public partial class MessageSent : Themelia.Web.Controls.SequenceUserControl
    {
        //- #OnPreRender -//
        protected override void OnPreRender(EventArgs e)
        {
            this.PageTitle = "Thank you for your message";
            //+
            base.OnPreRender(e);
        }
    }
}

You can base the title on whatever logic you choose.  For example, look at the following sample web site structure representing four fictional products:

  • http://www.mydomain.com/product/astra/feedback/
  • http://www.mydomain.com/product/supra/feedback/
  • http://www.mydomain.com/product/ipsum/feedback/
  • http://www.mydomain.com/product/lorem/feedback/

Say each one of these endpoints went to the same sequence.  In fact, this sequence has a view and user control for each product and the sequence automatically selects the proper view in the SelectInitView method based on the URL.  In this scenario we can easily automate our page title by creating a common base class for each user control:

public class FeedbackSequenceUserControl : Themelia.Web.Controls.SequenceUserControl
{
    //- #OnPreRender -//
    protected override void OnPreRender(System.EventArgs e)
    {
        this.PageTitle = Themelia.Case.GetPascalCase(Http.GetHttpPart(Http.Position.First)) + " Feedback";
        //+
        base.OnPreRender(e);
    }
}

This custom user control will set the title to the PascalCased product name plus the word "Feedback".  So http://www.mydomain.com/product/astra/feedback/ will automatically have a page title of "Astra Feedback".

On a different note, you can see in this scenario that sequences aren't just for wizards.  They can provide easy management and organization to related or patterened pages.

Data Binding

When using a sequence, you can do anything you would normally use in a page, this including data binding.  However, sequences give you an extra place to set your data sources.  Admittedly, this isn't really all that exciting, but given the following drop down list...

<asp:DropDownList ID="ddlTopic" runat="server"></asp:DropDownList>

...we can set the data source in the OnDataBinding method like this:

//- #OnInit -//
protected override void OnInit(EventArgs e)
{
    this.AutoDataBind = true;
    //+
    base.OnInit(e);
}

//- #OnDataBinding -//
protected override void OnDataBinding(EventArgs e)
{
    Int32StringMap data = new Int32StringMap();
    ddlTopic.DataTextField = "Value";
    ddlTopic.DataValueField = "Key";
    data.Add(-2, "Select a topic...");
    data.Add(-1, "-------------------------");
    data.Add(1, "General Comment");
    data.Add(2, "Help Request");
    data.Add(3, "Suggestion");
    ddlTopic.DataSource = data.GetDataSource();
    //+
    base.OnDataBinding(e);
}

The AutoDataBind property saves you from checking if IsPostBack is true as binding will only happen when IsPostBack is false.  If you want your data sources to bind during all post backs as well, then set the AlwaysDataBind to true.

This isn't a major abstract as ASP.NET does all the magic for you in classic ASP.NET any ways.  However, this does allow you freedom from IsPostBack mechanics and keeps data binding way from your Load event handler (i.e. Page_Load) method.

As a side note, you could have also used Themelia's MapDropDownList like this:

<t:MapDropDownList ID="ddlTopic" runat="server"></t:MapDropDownList>

With this code behind:

//- #OnDataBinding -//
protected override void OnDataBinding(EventArgs e)
{
    Int32StringMap data = new Int32StringMap();
    data.Add(-2, "Select a topic...");
    data.Add(-1, "-------------------------");
    data.Add(1, "General Comment");
    data.Add(2, "Help Request");
    data.Add(3, "Suggestion");
    ddlTopic.DataSource = data.GetDataSource();
    //+
    base.OnDataBinding(e);
}

With MapDropDownList the text and value fields are automatically set.

Site Wide Registration

One of the beautifies of ASP.NET controls is that you don't have to register them on a per page basis.  If you are going to be using sequences with any regularity, then you should probably register them into your web.config file.  Here's a snippet of that registration for quick reference:

  <system.web>
    <pages>
      <controls>
        <add tagPrefix="t" namespace="Themelia.Web.Controls" assembly="Themelia.Web"/>
      </controls>
    </pages>
  </system.web>

Links

Fundamentals of Themelia - Service Aliasing

Tuesday, October 14, 2008

This documentation has been updated for Themelia Framework 2.0 Beta 5.

In an effort to provide a clean web experience for users, we nee to remember that URLs aren't the only thing we can alias.  WCF gives us an incredibly powerful service infrastructure that crosses borders into the world of the web.  By creating JSON or XML-based WCF services for web use we can provide for a myriad of solutions and with Themelia we can make those services look good.

Before any examples are given its important to note two important things about this feature:  first, this feature is for web-based services and BasicHttpBinding services, not for non-web related WCF services like federates services, TCP services, or even WsHttpBinding services.  Second, this feature requires IIS7 in integrated mode.

To use this feature, first you need to "install" Themelia into the service host web site by referencing the Themelia.Web assembly, registering the themelia.web configuration section and adding the Themelia routing module to IIS7.  Here's the configuration section registration for quick reference:

<configuration>
  <configSections>
    <section name="themelia.web" type="Themelia.Web.Configuration.WebSection, Themelia.Web" />
  </configSections>
</configuration>

Also for quick reference is the routing module configuration:

<system.webServer>
  <modules>
    <add name="Routing" type="Themelia.Web.Routing.RoutingModule, Themelia.Web" preCondition="integrated" />
  </modules>
</system.webServer>

At this point, you're ready to use all of Themelia's features.  Of all of these features, one of the most simple to configure is service rewriting.  Here's an example:

<themelia.web>
  <webDomains>
    <add>
      <handlers>
        <add matchType="contains" name="ServiceAlias" text="/service/contact/" />
      </handlers>
      <aliases>
        <add key="/contact/" target="/Contact.svc" />
      </aliases>
    </add>
  </webDomains>
</themelia.web>

Now instead of giving out the endpoint /Contact.svc, you just give them /service/contact/.  You could obviously turn the URL in to /contact/ or anything else just as easily.  For now though, let's move to a more extreme example.

In addition to the many different data service mechanisms for which WCF provides, it also supplies awesome REST functionality and an amazing syndication feature.  The combination of these two concepts allows you to create RSS feeds with little effort.  The path to these RSS feeds also accept parameters so your RSS service knows how to generate the feed.  For example, while I use FeedBurner to provide the world with a feed for my blog, I must provide FeedBurner with the original feed.  They and only they have this feed.  The feed I give them looks something like this (no, this isn't really it):

http://www.netfxharmonics.com/Service_/Feed.svc/GetFeed/B166D85E-5B55-42a5-995E-F9D9958406E5/10

The point up to and including the Feed.svc is the service endpoint.  GetFeed is the method I want to call inside my RSS service.  Finally, the guid and the 10 are parameters passing to the GetFeed method.  This method does all the custom logic required for my system to create the feed.  Now notice two things: first, there is no way anyone should ever have to see this URL.  Not only does it contain internal mechanics, it's nearly impossible to remember and its prone to typos.  Second, WCF handles all the fanciness of creating the RSS feed.  If you would like more information on the syndication feature in WCF, see my Squid Micro-Blogging Library.  This library provides the fastest and most manageable way to create RSS feeds around. I've also open sourced it and explained the WCF syndication mechanics of it at http://www.netfxharmonics.com/2008/03/Squid-Micro-Blogging-Library-for-NET-35.

Now regardless if you are creating RSS feeds, the point remains that WCF provides the ability to create REST based services where the URL provides the parameters to the internal method.  Therefore, URLs can get really long, really fast.  Fortunately, as we have already seen, Themelia helps clean these up.  Here's how we can dramatically shrink the above REST-based RSS URL:

<themelia.web>
  <webDomains>
    <add>
      <handlers>
        <add matchType="contains" name="ServiceAlias" text="/feed.xml" />
      </handlers>
      <aliases>
        <add key="/feed.xml" target="/Service_/Feed.svc/GetFeed/B166D85E-5B55-42a5-995E-F9D9958406E5/10" />
      </aliases>
    </add>
  </webDomains>
</themelia.web>

All that stuff is now packed into /feed.xml.  You can do the some for the other types of WCF web-based services as well as for WCF BasicHttpBinding services.

Regular Expression Pattern Matching

One important thing you can do with service aliasing (as well as in page aliasing) is use a regular expression to create patterns of matching.  This allows you to setup a single alias for multiple services.  For example, instead of aliasing /service/comment to /Service_/Comment.svc, /service/label to /Service_/Label.svc, and /service/author to /Service_/Author.svc, you may just alias /service/([a-z]+)/([a-z]+) to /Service_/$1.svc/json/$2.

By way of example, let's say this is our WCF configuration:

<system.serviceModel>
  <behaviors>
    <endpointBehaviors>
      <behavior name="JsonPersonServiceBehavior">
        <enableWebScript/>
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <services>
    <service name="Sample.Web.Service.PersonService">
      <endpoint address="json"
                behaviorConfiguration="JsonPersonServiceBehavior"
                binding="webHttpBinding"
                contract="Sample.Service.IPersonService"/>
    </service>
  </services>
</system.serviceModel>

With this we can use the following as our Themelia configuration:

<themelia.web>
  <webDomains>
    <add defaultPage="/Page_/Information/Person.aspx">
      <handlers>
        <add matchType="contains" name="ServiceAlias" text="/service/([a-z]+)/([a-z]+)" referenceKey="svc" />
      </handlers>
      <aliases>
        <add key="svc" target="/Service_/$1.svc/json/$2" />
      </aliases>
    </add>
  </webDomains>
</themelia.web>

In this example, we are capturing the service name ($1) as well as the service operation ($2), thus allowing /service/person/GetPersonList to alias "/Service/person.svc/json/GetPersonList, which is exactly what's normally required.  We needed to do it this way because our WCF configuration has set a relative endpoint address ("json") for our service.  Since "json" is part of the internal mechanics of the system that has absolutely no meaning in the real world, the Themelia configuration removes it by capturing around it.

Loose Coupling

One extremely important point about rewriting services relates to coupling.  Tight coupling is a fast way to destruction.  When your physical path matches your logical path, you have a potential disaster on your hands.  When the world is accessing /Contact.svc, what happens when you need to move that file?  People are using this thing, so you can't exactly move it to /Service_/Contact.svc or anything.  Therefore, service rewriting isn't just there to make the URL pretty, it helps save you from insanity later on.

Links

Fundamentals of Themelia - Allowing Direct Page Access

Monday, October 13, 2008

This documentation has been updated for Themelia Framework 2.0 Beta 5.

When working with Themelia, you always think about the logical paths first and only later think about the internals.  If you are designing a framework, you always write the code to use the framework before you ever build it.  If you are designing the graphical layout of a web site, you focus first on how the user will use the web site and only later make it happen.  Designing the access interfaces to a web site is no different.

Therefore, when storing your physical pages in the standard Themelia locations of /Page_/ and /Sequence_/ (see details on sequences), you can be sure that no user will ever access them directly.  There's no reason to.  To a user, /Contact.aspx is meaningless as is /Page_/Information/Contact.aspx.  A user who wants to contact someone will logical access /contact/.

Because of this, Themelia naturally blocks direct access to /Page_/ and /Sequence_/.  A user who tries to hack your web site by accessing /Page_/Information/Contact.aspx or /Sequence/Checkout.aspx directly will be shown a standard ASP.NET HTTP forbidden error (the same error they would get if they were to access Contact.aspx.cs).  Of course, no user should ever know about these pages as pages are only advertised when they are linked.  You, as the architects and developers, have no reason to provide links to your web site internals.  Think of all ".aspx" files as having the access modifier "internal" (or Friend).

However, if you would like to provide direct access to your internal /Page_/ and /Sequence_/ pages, you may set the allowDirectPageAccess web domain collection configuration property to true.  Here's an example:

<themelia.web>
  <webDomains allowDirectPageAccess="true">
  </webDomains>
</themelia.web>

Though this is possible, it should really only be used for some type of testing or debugging or some type.  No user ever needs to see the internals of a web site.  Therefore, its highly recommended that you always keep this option disabled.

Links

Fundamentals of Themelia - Allowing Missing Trailing Slash

Monday, October 13, 2008

This documentation has been updated for Themelia Framework 2.0 Beta 5.

Themelia provides you with complete control over how you want your web site to be accessed.  For example, you might declare that /login/, /contact/ and /suggestion/ be access points into your web site.  However, you might, instead, declare /login, /contact/ and /suggestion.  But what about both?  Fortunately, with Themelia you have the option to allow access without the trailing slash through its acceptMissingTrailingSlash option.  Here's an example:

<themelia.web>
  <webDomains>
    <add acceptMissingTrailingSlash="true">
      <handlers>
        <add matchType="startsWith" name="Authentication" text="/authenticate/" />
        <add matchType="startsWith" name="Facebook" text="/callback/facebook/" />
      </handlers>
    </add>
  </webDomains>
</themelia.web>

Here we are allowing access to the Authentication handler via /authenticate/ or /authenticate and to the Facebook handler via /callback/facebook/ or /callback/facebook.

It's important to remember that this optional is on a per web domain level.  This will allow you to require the trailing slashes for your "service' web domain and declare them as optional for the rest of your web site.  Here's another example:

<themelia.web>
  <webDomains>
    <add acceptMissingTrailingSlash="true">
      <handlers>
        <add matchType="endsWith" type="Redirect" text="/vendor/xyzcorp/">
          <parameters>
            <add name="referral" target="http://www.xyzcorp.tempuri.org/referral/abccorp/" />
          </parameters>
        </add>
      </handlers>
      <fallThroughProcessor>
        <add type="ABCCorp.Web.Routing.ProductParsingFallThroughProcessor" />
      </fallThroughProcessor>
    </add>
    <add name="service" path="service">
      <handlers>
        <add matchType="webDomainPathEquals" name="Authentication" text="/authenticate/" />
        <add matchType="webDomainPathEquals" name="XYZCorp.ThirdParty.RegistrationHttpHandler, XYZCorp.ThirdParty" text="/registration/" />
      </handlers>
    </add>
  </webDomains>
</themelia.web>

Here we have two web domains: the default ("root") and "service".  The first will accept a request to /vendor/xyzcorp/ and /vendor/xyzcorp for a Themelia redirect, but /service/authenticate/ and /service/registration/ must be accessed directly (...you really don't want more than one endpoint to your service floating around!)

Remember, this is per web domain and, therefore, applies to that which is inside of the web domain.  This means that given acceptMissingTrailingSlash is set to true, while a path of /webdomain/myendpoint/ may be accessed by /webdomain/myendpoint, /webdomain/ is still accessible only by /webdomain.  Therefore, if you would like to allow access to the root of your web domains without the trailing slash, set acceptWebDomainMissingTrailingSlash to true on the <webDomains> configuration collection.  Here's an example:

<themelia.web>
  <webDomains acceptWebDomainMissingTrailingSlash="true">
    <add>
      <!--configuration omitted-->
    </add>
    <add name="service" path="service" defaultPage="/Page_/Service/Home.aspx">
      <handlers>
        <add matchType="webDomainPathEquals" name="Authentication" text="/authenticate/" />
        <add matchType="webDomainPathEquals" name="XYZCorp.ThirdParty.RegistrationHttpHandler, XYZCorp.ThirdParty" text="/registration/" />
      </handlers>
    </add>
  </webDomains>
</themelia.web>

Here the service web domain root is accessible by either /service/ or /service.

Now, when it comes to URL and service rewriting, everything that has just been mentioned still applies.  However, it's probably a good idea to remind you that the rewrite configuration key must match the text in the handler.  This is how they link together.  Removing the ending slash on the configuration key would disable the rewrite.  For example, the following configuration is correct:

<add acceptMissingTrailingSlash="true">
  <handlers>
    <add matchType="contains" name="UrlRewrite" text="/login/" />
  </handlers>
  <rewrites>
    <add key="/login/" source="/login/" target="/Sequence_/Security.aspx" />
  </rewrites>
</add>

However, the following rewrite would be incorrect for the handler registered above:

<add key="/login" source="/login/" target="/Sequence_/Security.aspx" />

If  you would like to disallow access to the endpoint with the trailing slash you must change both the rewrite key and the handler text.  Again, this is how they link.  The following configuration is also correct:

<add>
  <handlers>
    <add matchType="contains" name="UrlRewrite" text="/login" />
  </handlers>
  <rewrites>
    <add key="/login" source="/login/" target="/Sequence_/Security.aspx" />
  </rewrites>
</add>

Links

Introduction to Themelia Web Site Design

Thursday, October 9, 2008

When we design a web site, we absolutely must remember that the point of the web site has nothing to do with the web site itself or its the internal mechanics.  Therefore, one of the core purposes of Themelia is to put the focus of web development on the perspective of the user.  User's don't care about MyPage.aspx, Login.aspx, Home.aspx or other pieces of internal mechanics, they care about getting information (/information/, /report/, /contact/), making purchases (/products/hats/, /product/shirts/93873XL), and talking with friends (/chat/, /forum/).  Because of this, Themelia allows developers to keep the end in mind when providing a web solution.

In traditional ASP.NET applications, focus is normally placed on the ASPX files first and then later some thought might be put into cleaning up the web site for easy access.  That is, normally we think about default.aspx, login.aspx, and other files and later (if not temporally, then logically) ask the question "Can we alias or rewrite these to clean this up?"  This type of design process is completely backwards.  No one cares about your web site internals.  They only care about the results.  To the end of that result is the interface to the web site.  Here we mean, not the user interface, but how the web site is accessed.

When designing a web application using Themelia, the thought process starts with thinking about path access, not file access.  We also ask questions that developers rarely think about, but that are nonetheless critical: how do we want people to use this particular web site?  How should it be accessed?  What URL would a end user remember?  What API path would help developers understand the point of a particular endpoint?  Do we want to allow access to the entire application?  How can we make it obvious that different parts of a web site truly are different?  What happens when a person is not logged in?  How do I want Google to see my web page? How attractive is my web site structure?  Is the path to the corporate FAQ page easily readable on a business card?

These are questions of usability that, like questions of security, needs to be asked up front, not after the web site has already been designed, or, worst, built.  Themelia helps you create successful web sites by helping you implement solutions based upon answers to these questions.

If you are creating an e-commerce web site, for example, perhaps the answers to the above questions brings you to the conclusion that you should have a logical structure as follows:

http://www.mydomain.com/
http://www.mydomain.com/login/
http://www.mydomain.com/logout/
http://www.mydomain.com/reset/
http://www.mydomain.com/checkout/
http://www.mydomain.com/faq/
http://www.mydomain.com/blog/ (patterned access)
http://www.mydomain.com/blog/2008/09
http://www.mydomain.com/blog/2008/09/September-Newsletter
http://www.mydomain.com/blog/label/hats
http://www.mydomain.com/product/
http://www.mydomain.com/product/shirt/ (patterned access)
http://www.mydomain.com/product/shirt/F1927349/
http://www.mydomain.com/product/shirt/M8172648/

This logical structure has no direct relation to the physical structure at all.  In fact, the physical structure is absolutely irrelevant at this stage of site design and should be considered black boxed.  Fundamentally, what we are looking at with this logical structure is every publicly accessible way of accessing the web site plus two patterned areas.

The first patterned area would be the /blog/ path, which could be Minima (a.k.a Themelia Blog Services).  The second patterned area would be the /product/ path which allows public viewing of various products by product SKU and category type.  In fact, the /shirt/ part of the URL may be completely optional as you obviously have no reason to require it as idea of "shirt" is redundant given the SKU; it would just be there for easy viewing.  Both of these patterned areas as well as the other listed paths are easy to create using Themelia (more on this patterned access topic later on).

Themelia Web Site Structure

Of course, after (and only after) you have a logical structure approved for Internet (and real world) marketing, may create your physical structure.  Though you could do anything you want, Themelia provides a standard convention to follow in order to optimize Themelia usability.  Below is the basic structure of a Themelia web site:

Collapsed Themelia Web Site

Another major point of Themelia is that it provides unobtrusive ASP.NET development.  There's been so much talk about unobtrusive JavaScript in the past few years, it seems that people forgot all about ASP.NET and have let it go to the waste land.  In Themelia, you keep various entities together and keep as much stuff out of the root as possible.  In fact, when using Themelia, you don't even use default.aspx.

The basic folders for Themelia are: Config_, Page_, Resource_, Sequence_, and Service_.  In fact, Resource_ and Service_ are actually hard coded into Themelia to be special folders that may only be used for non-routing purposes (i.e. you can't alias /Resource_/ or /Service_/ or setup a handler in those paths).  Here's the basic run down of the various Themelia folders:

Config_ holds the various configuration sections of the web.config.  There's no reason to keep piling up the configuration in one monolithic file.  It screws with versioning, it kills your ability find elements, and it destroys cohesion.  By utilizing the extremely underused "configSource" attribute on configuration sections, you can greatly unobfuscate your ever growing web.config (or even app.config) files.

Page_ holds the various categories of web forms that your web site will be using.  In Themelia, you don't use web forms in completely the same way you would in classical ASP.NET.  Web forms are still web forms, but not all pages are web forms.  By defaulting your thinking to a web form, you are short circuiting the design process.  Web forms are for rich ASP.NET interaction.  If you need some sexy form of some great grid of data, perhaps you need a web form.  On the other hand, if you are handling a callback from PayPal, you need a handler.  Similarly, if you are going to do a checkout process, you may not need a classical web form, you might actually need a Themelia sequence, a topic discussed in a moment.

Web forms in Page_ are also not accessed directly.  Never will anyone ever access anything in /Page_.  Access to a Themelia web site is only through its logical structure; it's physical structure is protected.  To this end, use Themelia page aliasing for simple Page_ access.  Remember, the point of the system is the logical structure, not the physical structure.  You never want to think of the logical structure as being a byproduct of the physical.  Instead, think of the physical structure being enslaved to the logical.  Furthermore, remember we need to try to ignore the technology as much as possible.  Yes... ignore the technology.  We need to try to focus on what we want, not how to do it.  For exa