Revisiting Microsoft Forms: WebForms

This is the first of several reflections on Microsoft’s original forms solutions for .NET. In this post, I want to look back at ASP.NET WebForms, or more specifically System.Web and the Page life cycle. In hindsight, I think there were some really good ideas that were just hard to understand clearly given the dominance of OO, TDD, and DDD that were at the rising to the height of popularity while WebForms was the primary ASP.NET solution.

The Bad

When I started learning .NET in the mid-2000s, there were two UI platforms I learned: WebForms and WinForms. I never really liked WebForms and typically worked around a lot of its defaults. I was learning the .NET / Java style of OO after working with Ruby and Python, and I found it very strange that almost all of the methods were added to a code-behind file and were often protected event handlers. Any other functionality was typically supporting those event handlers. Here’s an example from a Microsoft Support page:

MyCodebehind.aspx

<%@ Language="C#" Inherits="MyStuff.MyClass" %>
<HTML>
    <body>
        <form id="MyForm" runat="server">
        <asp:textbox id="MyTextBox" text="Hello World" runat="server"></asp:textbox>
        <asp:button id="MyButton" text="Echo Input" Onclick="MyButton_Click" runat="server"></asp:button>
        <asp:label id="MyLabel" runat="server" />
        </form>
    </body>
</HTML>

MyCodebehind.cs

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace MyStuff
{
    public class MyClass : Page
    {
        protected System.Web.UI.WebControls.Label MyLabel;
        protected System.Web.UI.WebControls.Button MyButton;
        protected System.Web.UI.WebControls.TextBox MyTextBox;

        public void MyButton_Click(Object sender, EventArgs e)
        {
            MyLabel.Text = MyTextBox.Text.ToString();
        }
    }
}

I had a lot of questions:

  • Where’s the constructor?
  • How do I create reusable pages or controls?
  • What’s the process for adding things like data access?

I did not like some of the answers:

In all the blogs and discussions I had, these were all things to avoid, not embrace. What was this madness? I could go on with other things (only one form per page, ViewState, etc.) I didn’t like, but this post is not intended to focus on the bad parts but the good.

The Good

WebForms was built on top of System.Web, which was in turn built on top of IIS. (Why would you want to use another web server? But I digress….) ASP.NET’s tight integration with IIS used an application life cycle to handle web server events, upon which WebForms was built:

ASP.NET Application Life Cycle Overview

In order to write an application, you just needed to hook into the right event and amend the request or response appropriately. If that sounds familiar, then you may be thinking of one of these. It turns out that using a life cycle approach is a really good idea and is commonly used today.

IHttpHandler and IHttpModule

System.Web offered two interfaces for interacting with this life cycle: IHttpHandler and IHttpModule, which were mapped into the ASP.NET pipeline in order of their listing in the web.config used by IIS to serve a web application. The interfaces were very simple, and you could build solid applications by just implementing the interfaces.

I discovered these in my first attempts to circumvent WebForms, and they were the first approach I used in trying to find a way to build web applications that could run on different servers, which ultimately led to OWIN. In OWIN, and later ASP.NET Core, the use of middleware replaced both interfaces, but you can see how the concepts map to one another here.

ViewState and ControlState

I know what you are thinking: “Don’t these belong to The Bad section above?” Possibly. Let me talk this out a little, and then we can circle back.

HTTP‘s architecture is described by Fielding as REST. In section 5.1.3, he states:

We next add a constraint to the client-server interaction: communication must be stateless in nature, as in the client-stateless-server (CSS) style of Section 3.4.3 (Figure 5-3), such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.

https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_3

While ViewState and ControlState were typically the first things I disabled, the design did achieve the Stateless constraint. You could build apps where the client maintained state, and ASP.NET WebForms did this conveniently and relatively securely. Consider the current alternatives. What would those be? sessionStorage comes to mind, and it’s a browser API, not an application API. Perhaps ServiceWorkers?

I think this is an area that could use some additional exploration. I’m not saying we should bring back ViewState and ControlState as they were, but we may be able to learn a thing or two from them about how to build better stateless web applications.

Testability

This is the one I know you’ll scream at me for, but I have to bring it up. How can WebForms instruct us on testability? It was decidedly not testable! Well, that’s the point.

I learned awhile ago that the best way to make things testable was typically to write parameterized functions that accepted all dependencies as parameters and returned the values I wanted. I typically refer to this as a “seam”, where I/O, etc. are typically required.

As a concrete example, let’s use the sample above. Let’s suppose I wanted to do some sort of calculation in the click handler. I could embed all the logic directly within the click handler, but then I couldn’t test it. However, if I were to extract something from the EventArgs and pass it to a function, e.g. a static string LookupName(string value), I could easily test this function without any fuss and without even needing a Page instance.

Does this give me full coverage over my Page and all the interactions? No, but I can test the thing I care about, the LookupName function, and fairly safely assume that the framework, in this case ASP.NET WebForms, will handle the rest quite well.

Incidentally, I learned this approach through my use of F# to build applications primarily with modules (static classes) and functions (static methods). I found this approach so overwhelmingly successful, that I tend to use this in all my C# projects and as a litmus for determining what should and shouldn’t be tested.

Do you need constructors, injection, etc? Maybe. I certainly appreciate the ability to inject in newer tools that replace WebForms, i.e. Razor and Blazor. Again, I won’t say the solution was unquestionably perfect; however, I think the techniques we can learn here are worth learning and applying in newer projects.

Final Thoughts

I’m delighted in the direction Microsoft has taken with ASP.NET Core and pretty much everything they’ve done since they started investing in OWIN. I would not choose to build new applications in WebForms, nor would I be particularly excited about maintaining a WebForms application. However, I did find some reasonably comfortable ways to leverage WebForms in the past and felt I had some good success with it when it was the only thing I knew. Were it to have been released at a different time, I could see where it may have even had an even stronger impact on other tools and frameworks.

While a lot of new frameworks have life cycle hooks, I doubt they would claim to have been inspired by WebForms. There’s really nothing quite like the WebForms framework available on other platforms. That may be a good thing, but I suspect there are more good concepts, like client-side state management, that we could benefit from were it done well.

What do you think? Did I miss anything? Have I painted too rosy a picture, or not rosy enough? Leave a comment!

2 thoughts on “Revisiting Microsoft Forms: WebForms

Comments are closed.