Web Form 2.0 - Part 1

This post started as a rant about people learning ASP.Net as their first web development platform and consequently not really knowing what is going on under the hood.  I've interviewed quite a number of programmers over the last year and it's very rare to find anyone who would survive if you suddenly took the training wheels off.  Tertiary education is focusing more on turning out graduates that have useful skill sets in the short term than building a strong understanding of computer science fundamentals and the technologies everything is layered upon.

As I was writing I realised that the most usable web forms I have written have involved setting some of the ASP.Net magic aside and getting my hands dirty with the details.  Rather than rant at you I instead decided to discuss developing an example web form and show how a deeper knowledge of the layers underneath ASP.Net can allow us to provide a better end user experience and improve performance at the same time.

This information is particularly applicable to anyone interested in the upcoming ASP.Net MVC framework in which developers are much more intimate with the way the web works.

A Simple Form

For an example scenario I've chosen a simple online order form.  Imagine a user with a mail order catalogue in front of them who wants to quickly type in the code for each product and the quantity they want to order.  There are a lot of simple ways that the usability of this form can be improved such as adding interactive feedback on whether the codes entered are correct.

It just so happens that I've had to build this sort of form on a couple of occasions now.  Here is an example mock up:

Mock

View Source

A form like this can be thrown together pretty quickly using Visual Studio.  A repeater, a couple of text boxes in the item template, a button and so on.  Unfortunately the easy doesn't go much further.

ASP.Net tries to sweep a lot of details under the rug with varying degrees of success.  The trouble is that sometimes those details are important and sometimes those details can have a big impact on the success of a project.  Joel Spolsky calls this The Law of Leaky Abstractions.  As an example I borrowed a few lines from the HTML of the MSDN NZ site:

Problem-1

Ah, view state, I've only included the first few lines of it too.  ASP.Net is trying to present an abstraction where you can store state between page requests but this falls apart when that state becomes a major performance problem.  It is not all that uncommon to see pages where the developer has been careless with a data grid and built up 300k of view state which gets sent to the client and back every request.

There are a lot of tricks for keeping your view state down to a manageable level but doesn't it make you wonder how we got on before view state existed?

Here is another snippet from the same page:

Problem-2

Most concerning is the value of the anchors ID tag, ctl00_mainContentContainer_ctl38.  This phrase is repeated 3 times for each item in the list which can add significantly to the size of the page if the list is long.  The length of the IDs can be shortened by giving the mainContentContainer control a shorter name however we should not have to sacrifice code readability for such fundamental performance issues.

This is another case where ASP.Net is trying to help by ensuring that the ID tags are unique for every control on the page.  This can cause similar performance problems to view state however it also makes it more difficult for us to use JavaScript to develop client side functionality as we do not know what the controls final ID will be when we write it.  For more information on how these IDs get generated look up naming container in your favourite Google.

Bare Bones Markup

The HTML output for the example form is presented below.  I've used HTML controls directly rather than ASP.Net server controls and extras like ID attributes have not been included unless necessary.  The form isn't very functional yet but it's already pretty close to the final markup I will be using.  It's surprising how little HTML we can get away with and still have a very powerful user interface.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>Examples - Order Form</title>

</head>

<body>

    <form name="form" method="post" action="default.aspx" id="form">

<div>

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNzgzNDMwNTMzZGRitlNGlp3+dKeVTse2zabVSmkfSQ==" />

</div>

        <h2>Order Form</h2>

        <table>

            <thead>

                <tr>

                    <th>Product Code</th>

                    <th>Quantity</th>

                </tr>

            </thead>

            <tfoot>

                <tr>

                    <td colspan="2" align="right">Total Quantity: 0</td>

                </tr>

            </tfoot>

            <tbody>

                <tr>

                    <td><input type="text" name="code" /></td>

                    <td><input type="text" name="quantity" /></td>

                </tr>

            </tbody>

        </table>

        <input type="submit" value="Place Order" />

    </form>

</body>

</html>

I'm still figuring out what works well in the blog format and this is a big topic so I'm going to experiment with more episodic posts.  To give you a preview here are some of the topics I plan to touch on in upc0ming posts:

  • Getting your data back to the server without using server controls.
  • jQuery for client side interaction and feedback.
  • AJAX for interactive feedback from the server.
  • HTML as templates for modifying the form on the fly.

Keep an eye out for part 2 in the next few days.

Labels: , , ,

25/04/2008 04:34 AM (UTC -07:00)

ASP.Net Data Binding Patterns

One of the things we've found at work is that the code to bind your data to your page can quickly get out of control if you don't pay attention.  Things get worse with ASP.Net 2.0 as we introduce master pages and another place to control our data.  In this post I will outline what I have found to be the easiest guidelines for predictable and controllable data binding.

I assume you are already familiar with the basic data binding concepts and are trying to use them in a larger application.  If these concepts are new to you I suggest you spend some time at the official ASP.Net site which has a number of good tutorials.

Readability

The first issue we want to address is readability.  We should be able to easily understand when a control will be data bound and there should not be any guesswork involved in either developing or reading the data binding code.  This will help us develop quickly and avoid too much confusion when we have to make changes later.

Timing

Timing data binding correctly is a critical part of making it work.  We need to make sure data binding happens at the right point in the page lifecycle so that all of our dependencies are in place.  Sometimes we will need to delay binding of some controls until other parts of the page are ready.

Efficiency

We often fetch data from a database when we bind so giving some thought to efficiency is important to keep our web applications performing well.  We want to avoid binding controls more than once or binding things unnecessarily. 

DataBind()

All pages, controls and master pages derive from the Control class which provides a DataBind() method.  We will override this method to bind my data and to control how related controls are bound.  To keep things nice and simple the DataBind() method is going to be the one and only way to trigger data binding.

Binds a data source to the invoked server control and all its child controls.
MSDN

A lot happens inside a typical DataBind() call but the two things that are most important to us are mentioned in the description above.  First if the control supports data binding it will be bound to the data currently set in its DataSource property.  Secondly the control will call DataBind() recursively on all of it's child controls.

The reason this is so important is that it tells us the explicit order in which things will happen.  Data binding will works its way down the control tree from where we start until it reaches the bottom.  Parent controls will always be bound before their children and child controls will always be bound after the control that contains them.  The simplified control tree for a typical ASP.Net page might look like this:

  • Page (.aspx)
    • Master Page (.master)
      • Content Placeholder
        • Heading
        • User Control (.ascx)
          • Label
          • Drop Down List
          • Validator
        • Text Box
        • Footer

The most important thing that people don't realise is that the master page is a child of the page.  This means when we call DataBind() the page will be found first, followed by the master page and then any controls contained in the content placeholders on the page.  Keep this in mind if you are relying on the master page to set up any data for other controls.

Overriding DataBind()

By overriding DataBind() we gain control over the flow of data.  There are two important tasks to perform in override, setting up and data we are responsible and calling DataBind() on our children.

public override void DataBind()

{

    // Set up data

    dropDownList.DataSource = GetSomeData();

    repeater.DataSource = GetMoreData();

    textBox.Text = "Hello!";

 

    // Bind our children implicitly

    base.DataBind();

}

By calling the base classes Databind() method at the end of the function we are implicitly calling DataBind() on both the drop down list and the repeater we have already set up with data sources.  We do not need to call their DataBind() methods directly.

There are times when we have controls on the page that we don't want bound.  This might be because they are hidden or inactive for the moment.  When we want to do this we can remove the call to the base DataBind() and instead bind only the controls we want.

public override void DataBind()

{

    // Set up data and bind explicitly

    dropDownList.DataSource = GetSomeData();

    dropDownList.DataBind();

 

    repeater.DataSource = GetMoreData();

    repeater.DataBind();

 

    textBox.Text = "Hello!";

 

    userControl.DataBind();

}

If you use the second method you must remember to call DataBind() on all of your children that need it.  For example if you have a user control on the page you may need to bind it so that it can bind its children in turn even though it does not have a DataSource property.

Calling DataBind()

With the data binding infrastructure in place there is one important question we still haven't answered, where should we start the whole data binding process?  Fortunately the answer is usually quite simple, in each page class.  There are a couple of reasons this is the best place to call it from.  Firstly the page is the root of the control tree which means we only need to call DataBind() a single time to set up our whole page.  Secondly pages tend to differ greatly in their structure and workflow so calling DataBind() in the page will cater to more scenarios than if we called it in the master page or elsewhere.

public partial class WebForm1 : Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        if (IsPostBack)

            return;

 

        DataBind();

    }

 

    protected void Button1_Click(object sender, EventArgs e)

    {

        DoWork();

 

        DataBind();

    }

If the request is a post back we skip the data binding.  On a post back we normally have another event that is going to fire after the Page_Load and make some modifications to the page.  We will leave it up to that event to call DataBind() so that we do not overwrite the values the user posted us and so that we do not end up binding twice.

The only quirk with performing data binding at the end of Page_Load is that it happens before the Page_Load event of any user controls on the page.  I normally design my user controls so that this is not a problem but you can move the data binding to a later stage such as OnPreRender() if necessary.

post.DataBind().

Labels: , ,

16/04/2008 03:47 AM (UTC -07:00)

Object Oriented ASP.Net

While there are a huge number of articles about ASP.Net online very few of them discuss structuring pages and controls for readability and maintainability.  What general advice can we give to our programmers for building better web sites?  There is one key thing a lot of people seem to be missing:

Pages, master pages and controls are all objects and should be designed with the same object oriented principles you are already using elsewhere.

This post is based on a simplified version of a page one of our programmers was building at work.  The master page had a toolbar along the top with new, save and back buttons.  The page had a series of tabs for accessing different tools and the content of each tab was represented by a user control.  The code in question was in each of the user controls and looked something like this:

public partial class WebUserControl1 : UserControl, ITool

{

    protected void Page_Load(object sender, EventArgs e)

    {

        ((Button)Page.Master.FindControl("btnSave")).Click += Save;

    }

 

    void Save(object sender, EventArgs e) ...

The remainder of this post will discuss applying some of these principles to the code above.

Encapsulation

The first problem with the code above is that it relies on the master page being implemented a particular way and if it changes we wont know about it until run time.  Let's have the master page expose the page button via a strongly typed property whose implementation we can change without affecting any code using it:

((Site1)Page.Master).SaveButton.Click += Save;

This is a little better but out control still depends on being hosted on the right sort of master page.

Dependencies

Our user control depends far too much on being hosted in the right sort of page using the right sort of master page.  This greatly reduces the flexibility of the control making it harder to use, harder to test and harder to develop.  In general, the less our control assumes about the outside world the better.

Since there has already been plenty written about these ideas here are a couple of starting points:

With those ideas in mind let's take the master page out of the picture completely and instead require that our host page provide a Save event for us to subscribe to:

((WebForm1)Page).Save += Save;

We've certainly reduced our assumptions about the world around us but we are still casting to a particular type of page.  We could extract the event into an interface that must be passed to the user control which would be a form of dependency injection.  In this case I want to consider a related concept which can often simplify things for us.

Inversion of Control

Until now our user control has required an event so that it can listen to save events.  Let's consider for a moment what the single responsibility of the control is.  I'm going to go with:

To display information for editing and saving.

Note that our description doesn't mention the lifecycle of the page or interaction with other objects at all.  To allow for these let's define the pages responsibility:

To decide when and how each hosted tool interacts with the user.

Most importantly the details of each tool are not required for the page to do it's job.  Let's take control away from the user control and instead say that so long as it provides a save method the page will call it at the appropriate time:

public partial class WebUserControl1 : UserControl, ITool

{

    void Save() ...

 

public partial class WebForm1 : Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        Master.Save += Save;

    }

 

    void Save(object sender, EventArgs e)

    {

        currentTool.Save();

    }

Conclusions

Our user control no longer assumes anything at all about the environment it is hosted in making it much more flexible.  We have moved all of the life cycle logic into the page where we can write it once and it will be reused for any tool the page hosts.  There are no strings, casts or guesses about how the master page is implemented.

Give your ASP.Net interface objects as much thought as you do the rest of your code.  Future maintainers will appreciate it.

Labels: ,

12/04/2008 11:04 PM (UTC -07:00)

Making ASP.Net MVC ignore folders

Here's a small tip that might be useful to anyone playing with the ASP.Net MVC previews.

As you can see from the sample sitting there, Generic Error is an MVC application.  I am using Blogger to publish this content into a sub-folder called Blog but by default MVC takes over resulting in the following error when I try to view the blog:

The controller for path '/GenericError/Blog' could not be found or it does not implement the IController interface.
Parameter name: controllerType

To solve this you first need to convert the sub-folder to an application in IIS as shown below.  I am using IIS7 however the process for IIS6 is similar:

IIS

Then add a simple web.config file to the sub folder which removes the URL rewriting module from the chain:

 File Location 

Web-Config

And that is why you can read this post.

Labels: , ,

10/04/2008 04:57 AM (UTC -07:00)

Hello Blog

Looks like everything is up and running...
10/04/2008 04:14 AM (UTC -07:00)