When I started web development in the early 2000s, I was introduced to ASP.NET Web Forms.
There were .aspx pages, code-behind files, server controls, master pages, events, and state that seemed to follow you from one interaction to the next. I assumed this was simply how web applications were built. I did not yet understand that Web Forms was putting an unusually thick abstraction over the web itself.
It took Ruby on Rails arriving years later for me to see what had been hidden.
A request came in. The application did some work. It rendered a response. HTML forms submitted values. Links went somewhere. The web had a small, understandable shape, and Rails worked with it instead of trying to make it resemble something else.
Once I understood that, I was all in on Rails.
For years, I hated Web Forms for not being that direct. Looking back now, I think that judgment was incomplete.
Web Forms got plenty wrong, but it recognized something about application development long before the rest of the web industry settled on the same idea: developers wanted to build interfaces from components that owned behaviour and remembered state.
The web, translated for Windows developers
Web Forms was not really trying to teach Windows application developers how the web worked. It was trying to make sure they did not have to care too much.
The pitch was familiar. Put a control on a page. Set its properties. Attach an event handler. Let the framework manage what happened between the click in the browser and the code running on the server.
Microsoft’s description of Web Forms still makes that intention clear. It presented pages as an object model, controls as abstractions over HTML, and browser interactions as an event-driven programming model. The machinery that carried an event from the client to the server was meant to be automatic and mostly invisible.
That was a sensible answer to a real market problem. Microsoft had a large population of developers who understood Windows Forms, Visual Studio, properties, controls, and events. The web was growing, and those developers needed a way into it.
Web Forms gave them a bridge.
It also gave the rest of us ViewState, postbacks, generated markup, and a page lifecycle that could become its own area of expertise.
The abstraction was pleasant when everything stayed inside it. When it leaked, you had to understand two systems at once: the stateful application model Web Forms presented and the stateless request-and-response model the browser was actually using.
That was the source of much of my frustration. Web Forms made simple interactions feel magical, then charged you for the magic when the page did something unexpected. A button click might cross the network, rebuild a tree of server controls, restore state from a hidden field, move through the page lifecycle, run an event handler, and render the page again. The code could look like a desktop application even though the runtime behaviour was anything but local.
The framework did not remove the web. It made the web harder to see.
Rails pulled the curtain back
Rails felt like a correction.
Routes, controllers, views, requests, and responses gave names to what was actually happening. The framework was productive, but its productivity came from conventions around the web rather than an attempt to disguise it as a desktop event loop.
That distinction mattered to me. Once the request was visible, I could reason about the application from the browser to the database and back again. State did not disappear, but I had to decide where it belonged. An interaction that crossed the network looked like an interaction that crossed the network.
Web Forms began to feel like a detour I had been forced to take before discovering the real road.
Then frontend development changed.
Single-page applications became the fashionable answer to almost every problem. React, Angular, Vue, and the frameworks around them put components at the centre of the interface. Those components had their own state, responded to events, moved through lifecycles, and composed into larger applications.
The details were different. The shape was strangely familiar.
React now describes state as a component’s memory. Angular treats an application as a tree of components. Vue components are independent, reusable pieces of an interface, with each instance able to maintain its own state.
None of those frameworks is Web Forms with better marketing. They do not share its server-side execution model, and they are not descendants of it. Modern frontend frameworks generally keep interactive state in the browser and make communication with the server an explicit part of the application.
But they do validate the problem Web Forms was trying to solve.
Building a serious interface directly from pages, form submissions, and scattered scripts becomes difficult. Developers need boundaries inside the interface. They need reusable pieces that combine presentation and behaviour. They need a way to express what the screen should show as its state changes.
Web Forms saw that need early.
The right idea on the wrong side of the wire
Calling Web Forms ahead of its time does not mean ViewState was secretly a good idea or that we should bring back server controls.
The model had real costs. State could be serialized into a hidden field and sent back and forth with the page. Microsoft’s own guidance warned that this could create substantial page bloat. The page lifecycle was powerful, but it could turn an ordinary interaction into a question about whether code belonged in Init, Load, a control event, or PreRender. Generated HTML and JavaScript made the browser feel less like a platform you controlled and more like an implementation detail you inspected when something went wrong.
Modern component frameworks moved the same broad idea to a better layer. A React or Vue component runs where the interaction happens. Its state can remain local to that component. Rendering follows changes in that state without pretending a network round trip is a local button event.
They did not prove that Web Forms had the implementation right.
They proved that its instinct about how developers wanted to structure interfaces was not foolish.
That is the part I missed while I was busy hating it.
What I think about it now
I still prefer frameworks that expose the important boundaries of a system. Abstractions should remove repetitive work, but I want to know when the browser is talking to the server, where state lives, and which side is responsible for a result. Rails taught me the value of that honesty, and it still shapes how I build web applications.
I also have more sympathy for Web Forms than I used to.
It was built for a moment when the web needed millions of desktop developers to become web developers. It gave them a component model, event handlers, reusable controls, shared layouts, and managed state using concepts they already understood. The price was an abstraction that often fought the medium underneath it, but the bridge worked well enough that an enormous amount of software crossed it.
For years, I remembered the .aspx era as something web development had to outgrow.
Now I see it as an early attempt at a future that eventually arrived from the other direction. Web Forms tried to bring the desktop component model to the server-rendered web. React, Angular, and Vue made component-driven development native to the browser application instead.
Rails taught me why I hated Web Forms.
Modern frontend development taught me why it existed.
