With ASP.NET MVC 2 that approaches its final stage of RTM, more and more developers and architects, recent or early adopters of this powerful and alluring framework, begin wondering what would be the ideal structure of their controller classes and, more importantly, what is the "exact" role that the controller class play in the economy of an ASP.NET MVC application.
I haven't been a big fan of ASP.NET MVC in the beginning, and I was not certainly an early adopter of it. I severely contrasted a group (should I say, a school of thought?) in my company strongly advocating ASP.NET MVC over Web Forms about a year and a half ago when not even a Beta of ASP.NET MVC was available. As a matter of fact, that group prevailed, stable versions of ASP.NET MVC followed one another regularly, and we actually took the project home. Seamlessly. That gave me great fodder to think about ASP.NET MVC and study it from the underpinnings - the same runtime environment of ASP.NET Web Forms.
Today, as you may read also in my blog, I do recognize the inherent technical and architectural superiority of the ASP.NET MVC framework over ASP.NET Web Forms. Note that by recognizing ASP.NET MVC as superior all that I'm making is a relative statement. I still believe that ASP.NET MVC deserves more work and evolution to be ready to fully replace Web Forms and become the primary, if not unique, option for just any Web developers.
In this article, I'll focus on the core part of the ASP.NET MVC framework - the controller - and share some considerations about the ideal structure it should have as a class and its intended role.
Be Careful with Controllers
Controllers are the core of the ASP.NET MVC framework. Because of this, a blurred definition of their role and software structure may be as disruptive for your development efforts as, say, placing direct ADO.NET calls straight in your code behind event handlers.
In a way, ASP.NET MVC controllers have a lot in common with Web Forms code-behind classes. In ASP.NET MVC, controllers are the place where you start writing your code and implementing your use-cases. Similarly, code-behind event handlers are the first place where you start writing code in a Web Forms scenario. The simple fact of choosing ASP.NET MVC instead of Web Forms doesn't take you to write clean, nifty and well designed code. For sure, ASP.NET MVC shows you the way to go that is most appropriate for today's development. Web Forms, on the other hand, is based on a vision of software that was ideal for the time in which it was first devised - about a decade ago.
ASP.NET MVC shows you the way to go and also takes you at the beginning of the path. But at that point you are left alone to walk the way. And, in a way, you are left free of making your own right and wrong decisions. So pay a lot of attention when (and if) you add a new controller, and be careful with its methods and their implementation especially.
Creating Controller Classes
Visual Studio tools make creating controller classes definitely easy. I would even say that Visual Studio tools make it deceptively easy if I could be sure that this would not be perceived as a negative statement about Visual Studio ASP.NET MVC tooling. If you look at Figure 1, you get the idea that adding a controller class is a relatively simple task that tools can make even simpler by creating a bunch of stub methods for you.
Figure 1: Adding a controller in Visual Studio 2010.
At its core, a controller class is a class with a collection of public methods but the hardest part of it is the number and purpose of methods and the structure of the code you associate with each method.
Adding a new controller to an ASP.NET MVC application is never a task to be taken with a light heart. A controller is part of the presentation layer and exists to respond to some of the requests a user makes through the user interface. As such, the final set of controllers in an ASP.NET MVC application will effectively meet the needs of the presentation layer and map nicely to the URL scheme of choice. By the rule of thumb, you should endeavor to have one controller class for each significant entity in the domain of the problem that your application is called to solve. More precisely, you need a controller for each entity that is both significant in the domain and is exposed through the presentation layer. If no use cases exist for manipulating, say, an invoice then you probably don't need an InvoiceController class. At the same time, you may need a helper class in the data access layer for any server operation on invoice entities.
Code for an Action Method
More than the number of controllers and the list of their action methods, it's the structure of the code in the various action methods that determines how well designed an ASP.NET MVC application is. The ideal template of an action method can be outlined through the following steps.
- Process input values
- Perform the intended task
- Process calculated values and deal with errors
- Prepare the view model
- Select the view to render
The first step is mostly accomplished automatically through model binders. This fact moves the emphasis for developers from the binding algorithm to the proper definition of the signature of the method. The default model binder, in fact, works well for most applications. The need for a custom binder is commonly limited to situations in which you need to process special input forms with a granularity different from the granularity of the data types used in the signature.
The code used to perform the task associated with the action method is the most critical part. How do you want the controller to work? Do you want it to be the direct executor of any task or you want it to simply orchestrate the activity of other components? As I see things, the answer is simple. The controller is logically part of the presentation layer; as such, it should delegate the implementation of any business related work to components in the business layer. From the perspective of a controller, performing the task should mean invoking an external component - typically, a coarse-grained method on a service layer - and delegating any business rule validation and operation to it.
It should also be noted that placing business code outside the controller requires extra work and extra layers. Layered solutions are a proven way of dealing with complexity; but additional layers are also a certain way of adding some complexity. In simple scenarios, moving business code outside the controller makes the solution inherently more complex. To exemplify, if you're writing a simple data entry Web application with no significant business rules to validate, you can perform data access right from the presentation. It would not make perhaps for an "ideal" application, but it would do its job quickly and effectively.
When the controller yields to some business layer component, it may receive error messages resulting from the server operation. A responsibility of the controller is integrating these messages into the view. More in general, the controller is responsible for incorporating any calculated response into the view. ASP.NET MVC requires that the controller packs data into a dictionary (or preferably into a strong typed view model object) and passes it down to the view.
Extensions to the Controller
Even in moderately complex applications, the controller has a few dependencies - logger, services or repositories, perhaps some text provider for localization. How would you handle these dependencies? The most natural answer is that you can inject dependencies into the controller through one of the IoC containers available today such as Unity, NInject, or Autofac to name just a few.
This approach gives you the greatest flexibility, but forces you to register your own controller factory. A made-to-measure controller factory is necessary because the default factory can only use the default parameterless constructor of the controller. A custom factory, instead, can inject any additional parameter in the controller. For example, it could just use the container object to resolve the controller and all of its possible, chained dependencies:
This means that if you have a controller like the one below, and all involved types are registered with the container of choice, then you're all set with the simple registration of the factory shown in the preceding listing.
When you have to deal with the dependencies of a controller, you can also opt for a more lightweight solution that doesn't require the use of an IoC framework. Your controller class will look like the one below:
Invoked by the default factory, the default constructor will anyway instantiate correctly all of the dependencies your controller may have. Adding a new dependency is as easy as adding a new private member. At the same time, you have a second constructor where dependencies are listed explicitly thus making testing the controller just a breeze.
To a careless observer, the controller may look like the code-behind class of ASP.NET Web Forms, that is just the place where you start writing the code that performs the action. What would be the difference between ASP.NET MVC and Web Forms then? Oh well, controllers are separated from views and offer SoC and testability.
All of this is correct, but considering the controller simply the "place where you start writing code" for an action is a dangerous approach. The controller is part of the presentation layer and all you should really expect from it is delegation of the execution to some other module. That should be the rule; simpler approaches are still possible and, to some extent, encouraged, but only as the exception.
Dino Esposito is one of the world's authorities on Web technology and software architecture. Dino published an array of books, most of which are considered state-of-the-art in their respective areas. His most recent books are “Microsoft ® .NET: Architecting Applications for the Enterprise” and “...
This author has published 54 articles on DotNetSlackers. View other articles or the complete profile here.
Please login to rate or to leave a comment.