Stop Using ASP.NET Web Forms

I am active on Stack Overflow. I usually follow the asp.net tag pretty closely, as I've been working with the framework for years and I feel quite knowledgeable about it. I see a disheartening number of questions about ASP.NET Web Forms, particularly from people that appear to be just starting out and learning the framework for the first time. This is shocking to me. There are only a few reasons to learn this framework. For most people, it's an absolute waste of time. When I tell people this, they often want to know why.

There's so many reasons why! And it's a shame they even have to ask. People really need to take the time to research a framework more carefully before learning it, because investing time in a framework that is difficult or dying is a waste of valuable time. We can only learn so many frameworks in depth, you should use it to learn one that is easy to work with and has a bright future.

Why is Web Forms so bad?

No feature updates

Web Forms hasn't received a major feature update in a long time. It actually came as a shock when .NET 4.7.2 in April 2018 added a feature (half-baked Dependency Injection)! No new features being added is a sure sign that no innovation is happening. And if no innovation is happening, it's getting left behind because other frameworks are adding great features that make them easier to work with and faster.

Viewstate ignores how the web works

The web is normally stateless. This means that when a web server receives an HTTP request, it processes that request without past knoweledge of what previous requests may have come from that particular client. There are ways to add state, such as cookies. Web Forms adds another approach: Viewstate. Viewstate is a hidden form element added to the DOM. It contains the serialized state of all the controls on the page as of when the page was rendered. When the form gets submitted (via a postback) that information is sent to the server, which can compare the viewstate with the new values that get POSTed and determine what has changed. Controls that may have initially had their values set when the page first don't need to have them manually reset, as the Viewstate will be able to persist their previous values.

Sounds great in theory. But the problem is that this adds a large amount of redundant information to the page that must be sent to and from the server with each request/response and then maintained in the DOM. In the case of data-heavy controls such as a GridView, this large chunk of serialized data can significantly slow down everything down.

You can disable ViewState per control or at the page/application level. Which is probably the best thing to do. But beware that many legacy sites were designed with the assumption that ViewState is enabled. By turning it off, you're likely breaking functionality and now every piece of your UI needs to be tested again. And since people generally don't create new Web Forms sites anymore, turning it off up front is a moot point.

Visual Designer

Web Forms has a visual designer, where you can drop and drop controls onto a surface and visually see how your page will look. Or at least, that's the idea. Unfortunately, in practice it doesn't work so well. Most web pages will use some amount of JavaScript and CSS to format a page after it loads - the visual designer often doesn't take this JavaScript into account or does a poor job of applying the CSS. Also, dropping controls onto pages and moving them around can result in ugly markup that's difficult to maintain - and you will have to directly modify the markup at some point to fix issues. Good luck messing with the spaghetti markup!

Like ViewState, you can chose to not use the visual designer. It's what I do when working with Web Forms. But the reaeson I mention it in the first place is that so many new developers are attracted to the idea of Web Forms because of its visual designer, and they don't think about or ignore the ramifications of using it.

Mangled client ID's

When server side controls (elements declared in the ASPX markup with the runat=\"server\" attribute) are created, we normally assign them and ID, such as \"FirstNameTB\" for a text box that the user should enter a first name into. When the ASPX is rendered to HTML and sent to the client, the element of that ID on the client side is often not what the developer expects. Controls that are nested (and they most often are!) inside a content page, or a user control (.ascx) or some other container (such as a Panel control) will have the names of the parent containers appended to the ID on the client side. This is done because it's not valid to have multiple elements within the DOM on the client with the same ID, and by the composite nature of master pages, templated user controls and other containers, it's conceivable that you could have two elements with the same server side ID declared in different places. The framework \"helpfully\" tries to prevent conflicts on the client side by appending the container names in front of it, thus ensuring the rendered page has unique ID's for all elements.

This is often suprising to developers new to Web Forms when they try to write some JavaScript that tries to interact with these elements by ID. There are some possible solutions:

It's a problem that can be worked around, I just don't like that I have to do so in the first place.

UpdatePanel

The UpdatePanel is a control that you can add to your page, and other controls you place insisde the UpdatePanel can be updated without having to refresh the entire page. It does this behind the scenes by using XmlHttpRequest (just like AJAX). It seems magical at first. But many developers end up regretting ever using it.

When an UpdatePanel refreshes, it posts the entire form pack to the server side. That can potentially be a large amount of data! The entire page lifecycle will run (such as Page_Load) so the developer needs to take care to check if it's a PostBack so that they don't reinitialize values. This ends up being extraordinarily resource intensive. Even though these postbacks happen asynchronously, they happen slowly and use a lot of server resources due to the entire page lifecycle running again. That slows things down further.

What should you do instead? A little custom JavaScript to communicate data with the server side will be a lot easier to debug, and you can choose to send only the data that the server needs, and the server only needs to response with the data actually needed by the client.

Developers sometimes pair the UpdatePanel with a Timer control, so that the page can refresh certain parts at regular intervals. Now you're polling the server, sending massive amounts of data on a regular schedule, even though data may not have changed!

What should you do instead? Push data from the server to the client, only when the data changes. You can push data from the server to the client via technologies like WebSockets, something that Microsoft's SignalR framework makes very easy.

SqlDataSource

SqlDataSource is a control that you drop on the page to enable getting data from or into a relational database. It's often paired with a data control, such as a GridView, DetailsView, or Repeater. It takes one or more CRUD commands (INSERT, SELECT, UPDATE, or DELETE) to allow it to perform this wor. The command is embedded right in the ASPX markup.

This should set of red flags in your mind if you know anything about seperation of concerns. Your UI layer (am ASPX page is firmly in the UI layer) should not be strongly tied to your database layer. This makes it difficult to change the storage layer without breaking your UI, and it makes it difficult to test your page without having a real database. You also don't get some of the design time benefits that working with strongly-typed model classes can offer, such as compilation errors when you mistype the name of a column.

What should you do instead? Create a proper database layer following the repository pattern.

public interface IProductRepository
{
    public Product GetProductById(int id);

    public IEnumerable<Product> GetAllProducts();

    public void CreateOrUpdateProduct(Product product);

    public void DeleteProduct(int id);
}

public class SqlProductRepository : IProductRepository
{
    // Implementation here
}

Then your form should depend on an IProductRepository (not SqlProductRepository!) that should be injected via DependencyInjection. Or better yet, have your UI layer depend on a busineess logic layer, and that business logic layer should handle all interaction with the IProductRepository.

Half baked Dependency Injection

Web Forms has had dependency injection for a while. Certain DI containers such as Ninject were able to hook into events in the ASP.NET pipeline and do property injection of dependencies into some Web Forms classes. But property injection is generally considered inferior to constructor injection - because we can find out much earlier if our dependencies aren't satisfied.

ASP.NET 4.7.2 added support for constructor injection. No container is implemented by default, you need to add additional libraries such as AutoFac, Unity, Ninject, or Simple Injector which can provide an IServiceProvider (an interface built into the framework for resolving dependencies). However, you must manually register any of the Web Forms types that you want to resolve, because they're not automatically reigstered. So you have to wrap the container of your choice with some reflection logic to resolve the Web Forms types yourself.

public class AutofacServiceProvider : IServiceProvider
{
    private readonly ILifetimeScope _rootContainer;

    public AutofacServiceProvider(ILifetimeScope rootContainer)
    {
        _rootContainer = rootContainer;
    }

    public object GetService(Type serviceType)
    {
        if (_rootContainer.IsRegistered(serviceType))
        {
            return _rootContainer.Resolve(serviceType);
        }

        return Activator.CreateInstance(serviceType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, null, null);
    }
}

What a mess!

Difficult to test

Mangled client ID's, difficult Dependency Injection, tight coupling of the UI to the back-end all make it extremely difficult to unit/integration test a Web Forms project. And untestable code often leads to unmaintainable code.

What should I learn instead?

When should I learn Web Forms?

I've often heard it said that Web Forms is faster to make apps with than alternatives such as ASP.NET MVC. Anyone saying this shouldn't be taken seriously. Given the same amount of time to learn both frameworks, you can accomplish just about any task in the same amount of time. However, applications written in more mature frameworks tend to be easier to maintain and debug, because of the more mature coding practices that they enable you to take advantage of - such as tight control over your rendered HTML markup, seperation of UI logic from business logic, and ability to test more layers of the application. I've heard quite a few people say this \"Web Forms enables rapid development!\" line, and then I look at their code and it's utter garbage - and what's worse is they don't understand why it's not good. They're often not aware of what SOLID code is. Web Forms proponents are usually older developers that learned Web Forms at point in the early 2000's and didn't bother updating their skillset. In an industry that moves as fast as web development, that's an unforgiveable sin.

There's very few reasons to learn Web Forms. Companies sometimes have old legacy Web Forms apps - this is a sign that the company isn't interested in keeping their code base up to date. Before accepting a position at a company that has Web Forms apps, ask if there's a plan in place to modernize the app and get rid of Web Forms. See if they're aware of the many downsides or if they're in denial. Pay close attention to their answers - you probably don't want to accept a position where you learn a framework that has very little future and that will be frustrating to work with.

If you're in an education envirornment and your teacher is teaching Web Forms, this is a red flag that the teacher is not in touch with the industry. Web development changes rapidly - a teacher that isn't keeping up probably isn't worth your money. You might consider asking them to update their course materials, or even raise the issue with their supervisor.

Web Forms Legacy

A long time ago, Web Forms was a great framework to work with. It brought over many concepts that were familiar to Windows Forms developers and allowed them to quickly transition to creating web applications, without needing to actually learn how the web works. But that was a long time ago, when rich web application were still in their infancy. Now users expect far more of their web applications. They want quick client side validation without having to submit a form. They want sections of pages to refresh without refreshing the entire page. They want data to be pushed to their browser without needing to manually refresh. They want quick loading and good mobile support. And they want sites that can be updated rapidly, meaning those sites need to have well maintained and code that's easy to automatically test. Web Forms either doesn't work with any of this, or requires so much deviation from standard Web Forms patterns that you might as well not use Web Forms at all.

Web Forms was highly successful for its time, and the underpinnings of it were able to be abstracted away to make it possible to create ASP.NET MVC, which eventually was rewritten as ASP.NET Core MVC. So I'm very grateful that Scott Guthrie and the rest of the team at Microsoft created it. But it's time for us to transition away from it and stop teaching it to new developers. Let it rest in peace.