This article is the result of some discussion that were triggered by a question asked during an ASP.NET MVC class I taught recently. Is a controller method the same as a postback event in Web Forms? Overall, the topic of controller methods is nothing new for seasoned ASP.NET MVC developers: However, for developers with a strong Web Forms background it represents one of the highest hurdles in the race to refresh their ASP.NET programming skills.
With ASP.NET 5, Microsoft explicitly abandoned the Web Forms programming model, so Web Forms-addicted developers will need to plan to upgrade their skill-set. Web Forms will, of course, not disappear overnight and its use remains an option rather than a crime. All considered though, this is an exciting time to look around and explore whatever form of life exists beyond Web Forms.
This article aims to explain the role and mechanics of controllers to Web Forms developers. At the same time, it might interest those ASP.NET MVC developers who are willing to refresh the basics of ASP.NET programming. This article might serve as a timely reminder, especially if you plan to code for the .NET Core framework in ASP.NET 5.
From Pages to Actions
Back in 1990s, the Active Server Pages (ASP) technology was a blast. Web developers really liked the idea of designing Web pages as HTML-based templates interspersed with some script code executed at run time on the server side. In this way, each view could be planned around server-held data but could be built in a much more flexible way than was possible with plain HTML. Classic ASP had one main merit: it made web programming really easy for many developers. Over a decade ago, ASP.NET upgraded classic ASP to the rank of compiled code and totally superseded it a couple of years later. Recently, however, a perspective customer called me to review a newly created portal and I was shocked to discover that it was actually written-in 2015-with ASP.
I wouldn’t want to recommend using ASP for building a web site today, but it was reassuring to see that it was still possible to use ASP effectively to arrange a web portal. For me, it puts ASP.NET 5 into perspective Maybe it isn’t so critically important for companies to upgrade to ASP.NET 5 as soon as possible. Once you’ve mastered a platform for building web solutions, you can keep on using it as long as it obeys two simple rules:
- With the platform and technology of choice you can easily achieve exactly what you need
- You are careful to avoid being bound to a legacy technology
Is Web Forms a legacy technology?
Web Forms is over ten years old, but more significantly the pillars of the web have changed since the time Web Forms was devised. In general terms, Web Forms is a legacy technology; but not all applications based on Web Forms are necessarily legacy just because they have been built with what is now a legacy technology. We write software applications to serve customers; not to please vendors of software platforms. I’d say that if a given technology can be up to the business task, and the team feel comfortable about using it, why look elsewhere? Microsoft itself will not probably make any further improvement to Web Forms-and this is totally understandable-but still Web Forms will stay alive and kicking for a few more years.
The fact is that the vision of the web behind Web Forms is no longer the most up to date one. Microsoft have shown, with the changes in ASP.NET 5, that the vision of a page-based web world is clearly gone. Web applications no longer expect users to view pages. Modern Web applications, instead, expect users to command actions. Hence, every user-to-application interaction is now in the form of calling an action on a controller component.
ASP.NET MVC was designed to focus on the actions that a user may take from within a HTML view. It has a different view engine and allows much more control over the generated markup. In a way, ASP.NET MVC is action-centric and also close-to-the-metal of HTTP and HTML.
Behavior of Controllers and Postback Handlers
As an ASP.NET MVC developer, you must think differently to a Web Forms developer. In Web Forms you request the content of an ASPX server file. The overall design focuses on the page to render and the page interacts with the system posting data to the same URL. The state of the page is moved back and forth with each request and response. In ASP.NET MVC, instead, you simply request a URL whether through a GET or POST HTTP action. The request causes some action to take place and some response to be generated. No state whatsoever is implicitly maintained, outside the state that you deliberately decide to maintain through session and cache.
In ASP.NET MVC, you organize the application around a few controller classes and set up a bunch of routes to map URLs to controller methods. From the URL, the ASP.NET MVC infrastructure figures out the controller class to invoke, and the method to call, and then calls it. Any posted data-query string, forms, segments of the URL-may become parameters of the invoked method. Executing the action and producing the response are distinct steps taken care of by distinct ASP.NET MVC subsystems-controllers and the view engine.
Although the context is different, controller methods and postback event handlers play the same role. Both are the entry point in the server-side processing machinery for each request coming from the user that must be served by the application. Both controllers and postback event handlers are conceptually part of the presentation layer. Both are deeply immersed in the ASP.NET HTTP context. Both must have full access to the request and response streams, to the caching and session infrastructure and identity information.
Nearly every Web Forms developer has, at least once, implemented bits and pieces of business and data access logic right in the postback event handler. Likewise, ASP.NET MVC developers do it in some methods of some controller class. No big deal, you may say, the code still works and the application still runs some kind of business. From a design perspective, though, it is hardly a minor sin.
Whether you still work on Web Forms or you’ve moved to ASP.NET MVC, you will adopt nearly the same strategy to articulate the structure of requests’ entry points in code. The core steps associated with a request are the following:
- Collect input data.
- Performs the specific task acting on the backend of the application.
- Collect the information that form the state of the next view to present to the user.
- Invoke the next view.
These four macro steps are accomplished in different ways in Web Forms and ASP.NET MVC. It is trivially easy in ASP.NET MVC to collect input data: all you do is to define parameters on the controller method whose formal names match the key name of posted data or query string parameters. In Web Forms, instead, the input state for the task is read from the public properties of server controls in the page. The performance of tasks simply consists of calling some distinct piece of code passing some input and getting some output. There are no logical differences here between ASP.NET MVC and Web Forms. Any output values being returned by the task must be stored in some way so that it becomes available for the generation of the next view. In ASP.NET MVC, you have a few options. For example, you can copy individual values in the ViewBag dictionary or pack everything into a user-defined view model class. In Web Forms, output values will be copied directly to the public properties of involved server controls defined in the page. Invoking the next view doesn’t require an explicit action in Web Forms from the developer. In ASP.NET MVC, instead, the action consists in invoking the view engine passing the Razor template of the view to use and the instance of the view model class with the data to incorporate in the view.
Fat-free Entry Points
How much code should you have in a request entry point such as a postback event handler or a controller method? The code that captures input data and prepares output data is mandatory and can’t be avoided. And so it is for the code that invokes the next view. In ASP.NET MVC, collecting input data is nearly a codeless step as it happens automatically via the model binding layer. You just declare a made-to-measure signature for the controller method and the system does everything else.
The sore point is the amount of “fat” you have because of the task and its implementation. Ideally, the entire implementation of the task should be moved out of the controller class and ASPX code-behind class. Where does it belong? That is part of the application layer or, in simpler cases, it belongs to the data access layer. In ASP.NET MVC, a controller method is as thin as below:
public ActionResult SomeAction( /* parameters */ )
var service = new ApplicationLayerService();
var viewModel = service.SomeAction( /* parameters */ );
Calls to database, processing logic, calls to external web services are all orchestrated from within the application layer. Each method in an application layer service class will map to a specific user request and is bound to a specific controller action method. It gets the input data that the controller method receives and returns a custom view model that is readymade for the needs of the next view to show.
The controller acts as a proxy; it gets requests wrapped up in a HTTP context, extracts the raw information and uses that to plan some business action. It’s not much different in Web Forms. The algorithm is the same, but it takes a few more lines of code.
public void Button1_Click(Object sender, EventArgs e)
// Collect input data from server controls
var inputModel = new MethodInputModel();
inputModel.Xyz = Control1.Xyz;
var service = new ApplicationLayerService();
var viewModel = service.SomeAction(inputModel);
// Prepare the state of the page for rendering
Control1.Xyz = viewModel.Xyz;
Removing code “fat” from request entry points has a few benefits.
Benefits of Fat-free Entry Points
I believe Microsoft did a good job with ASP.NET MVC but, for some reasons, the final message that developers got out of it was to some extent misleading. ASP.NET MVC, says the message, is designed from the grounds up to be testable and to expose its internal infrastructure as distinct layers in full observance of the principle of Separation of Concerns. Right, but it doesn’t mean that, just because you use controllers, your code is highly testable, readable and maintainable.
From a strictly mechanistic perspective, a controller is just the same as a postback event handler. The only difference is that with ASP.NET MVC Microsoft introduced an HttpContextBase dual class that can be mocked up on purpose. (The same is impossible to do with the original HttpContext class.)
Beyond the entry point level of controllers, any further (desirable) layering is up to you, in ASP.NET MVC as well as in Web Forms. To achieve real code testability, you’ve got to separate the entry point skeleton from the rest of business code. You can do that in Web Forms as well in ASP.NET MVC; and it’s your design effort.
By following the pattern outlined above, you keep any HTTP-related content separate from the business logic. The application layer code-and any other code in layers underneath it-are completely independent from the HTTP context. If HTTP values are to be processed as input, you just inject them as raw values in the application layer via input parameters. The entire application layer is fully testable in isolation. And this happens in both ASP.NET MVC and Web Forms.
If you manage to remove most of the code “fat” from the controller class, it then makes little sense to test the controller class. You can still do that, but it doesn’t really give you any significant benefit. With such a simple and minimalistic controller class, what are you going to test? Which kind of regression are you in fear of? Microsoft is expected to test the overall behavior of controller base classes; not you. You are responsible instead for whatever is called from within controllers.
The Bottom Line
In my opinion, the entire HTTP mocking infrastructure is not strictly necessary, though it’s great to have just in case. The testability of ASP.NET, both MVC and Web Forms, passes through removing fat from request entry points rather than from mocking the HTTP context and expecting to have injection points wherever possible.
The most testable code is the code you write with dependency in mind.
If you’re coming from Web Forms, be ready to learn a bit more about software design because, in MVC, you have none of the readymade components (read, server controls) that you used to in Web Forms. You won’t be learning MVC as you go, by trial and error. If you take this approach, the learning curve will inevitably get far steeper. MVC is about actions; and actions require a clear input and return a clear output. The input is plain data, and the output is plain data incorporated in a markup template. The core structure is the same in ASP.NET Web Forms and MVC, but it may not be obvious to see.
Just hope it’s clearer now!