The Model-View-Controller (MVC) pattern is not by itself a guarantee of clean code and neatly separated layers. The controller simply ensures that any requests are routed to a specific method that is responsible for creating the conditions for the response to be generated and returned. The key question is this: ‘Are you sure that all the responsibilities that are thereby assigned to the controller method make for a thin layer in which simplicity and the ‘single responsibility’ principle are honored?’
Even in moderately complex applications (10-15 use-cases and some 30 domain entities), the steps that each controller method has to take in order to trigger the view engine are numerous enough to produce rather bloated code. In this regard, a controller class often ends up as a god-object-like sample code-behind class, padded with too many lines of code and nowhere near any ideal standard of testability and readability.
This article intends to show a slightly different approach that reduces the role of the controller to that of a mere part of the ASP.NET infrastructure, and introduces a new, more specific component, called the orchestrator: This component ties up the controller with the application logic.
Beyond Controllers
The primary message of this article is that, as a developer, you should do all you can to keep controllers as simple and thin as possible and move any code that implements application logic out of the controller class. The orchestrator class is a new class where the application logic should go. The controller just grabs the data from the presentation layer by whatever means (forms, query string, URL), and pass it to the orchestrator. The orchestrator then handles the request, which may involve changing the representation of data (from presentation model to domain model) and placing calls to the middle tier.
When I bring this argument to the table, I often receive the following objection: “Do you mean we should take any logic out of the controller to another class? What’s the point of transforming the controller into a pass-through class?”
I see two reasons for taking code out of the controller class. One is to introduce an explicit decoupling layer between controller and backend. This layer, you can call it the application layer, orchestration or just “service layer” after the underlying pattern, represents an explicit implementation of the various steps that a given use-case requires. In this way, from the beginning you reason in terms of your use-cases and create the classes that produce a better implementation. While focusing on the actions required by a given use-case, you can clearly see the data that your methods need to receive, and to return. In doing so, you reason in terms of plain data types, data transfer objects or,( to use an ASP.NET MVC terminology) view model objects. The methods of orchestration classes are therefore invoked from within controller methods and receive, and return, plain data transfer objects ready to be served to the view engine so as to produce the final HTML view.
Where am I heading here? I’m just getting towards the second reason for moving any significant code out of the controller class. You have no need to mock up the HTTP context. Any invoked controller method, in fact, will extract out of ASP.NET intrinsic objects (e.g., Session, User, Request, Cache) any significant data and pass it to orchestration methods as standalone data.
In this way, you need to focus your testing efforts on the orchestration classes rather than controller classes. Orchestration classes are entirely out of the ASP.NET context and can be easily tested in full isolation.
Skeleton of a Controller Class with Orchestrators
I see a 1:1 correspondence between controller classes and orchestrators. However, at the design level you first focus on orchestrators and then infer any controllers you need from there. If needed, at this point you also fix your URL routes in the global.asax file.
Determining the number and type of controller classes is always a critical design point for ASP.NET developers and architects. You mostly figure them out of use-cases anyway, but I’ve found out that shifting the focus to orchestrators and use-cases makes it a more natural process. On the other hand, you’re going to write some code that solves a specific use-case. Hence, you should focus on the logic required by the use-case; not a mere (yet important) part of the infrastructure such as controller class. Here’s some basic code that illustrates the structure of a controller with an orchestrator. As mentioned, I usually envision one controller per orchestrator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class SampleController : Controller { private readonly ISampleOrchestrator _orchestrator; public SampleController() : this(ISampleOrchestrator orchestrator) { } public SampleController(ISampleOrchestrator orchestrator) { _orchestrator = orchestrator; } // More code here ... } |
Because the controller class clearly has a dependency on the orchestrator, you might want to design the controller class to support dependency injection. Therefore, as a first step you abstract the orchestrator to an interface. The code above uses the poor man’s dependency injection schema (an overloaded constructor); feel free to rewrite that code to use your favorite IoC framework.
Before I go any further with the description of a possible implementation of controller methods, let me share a thought about the use of dependency injection in this particular case.
It’s known that writing clean code costs you a few extra lines and a few extra brains cycles. For this reason, most developers in a constant hurry often decide that some dusty and lightly dirty code is acceptable. This is a deadly sin if you introduce less-than-clean in critical places. It is a forgivable sin if you don’t code against an abstraction where it is not strictly required. The bottom line is that you don’t need clean code everywhere. You don’t need to break apart just any dependency your classes may have.
I’d say that you don’t strictly need the burden of dependency injection in this particular context. The controller will always work with an orchestrator; but the resulting code in the controller class is really trivial and may not need testing at all. In addition, there are high chances that you won’t be reusing the orchestrator anywhere else in your code. In this case, a form of tight coupling between the controller and its orchestrator doesn’t look like a deadly sin.
Implementation of Action Methods
The implementation of action methods in a controller backed by an orchestrator is very minimal. It consists of collecting any data the orchestrator requires and placing a call to an orchestrator method. The orchestrator method returns a view model object that is ready to be passed down to the view engine. In its simplest form, here’s a possible structure for an action method:
1 2 3 4 5 |
public ActionResult Index() { var model = _orchestrator.GetHomePage(); return View(model); } |
As you can see, the controller method is a mere pass-through, because all the work is being done by the orchestrator. There’s really no need to unit test such a controller. Unit testing, instead, is a key element for the orchestrator. The good news, however, is that the orchestrator doesn’t have any sort of dependency on the ASP.NET runtime and there’s no need to mock up the HTTP context.
Note: There’s a good chance you’d still want to do some integration testing or system/end-to-end testing
Thanks to the redesign of part of the ASP.NET runtime that has been done with ASP.NET MVC, is no longer a mission-impossible task to mock the HTTP context. However, I would say that it is not quite a walk in the park: the deeper you use Request, Session or Response the harder it may become to write appropriate unit tests for controller methods.
Let’s consider a scenario in which you need to process some session state data in order to produce the next view-in an e-commerce application at some point the user clicks to proceed to the checkout page.
1 2 3 4 5 6 |
public ActionResult Checkout() { var cart = Session["ShoppingCart"] as ShoppingCart; var model = _orchestrator.ProceedWithCheckout(cart); return View(model); } |
The ProceedWithCheckout method gets an internal ShoppingCart object that is filled with any goods the user has added. The shopping cart object is usually stored in the session state; extracting it from session and passing it around as an independent object removes the need of mocking the HTTP context when testing the actual logic behind the checkout operation.
More in general, I’d say that the controller method is responsible for retrieving any data the orchestrator needs from whatever source it has access to. Being part of the ASP.NET MVC infrastructure, the controller has total access to runtime objects; by extracting data from there it can decouple the real logic of the action from the surrounding environment. Here’s another example:
1 2 3 4 5 6 |
public ActionResult GetNews(DateTime startDate) { var user = User.Identity.Name; var model = _orchestrator.FindNews(user, startDate); return View(model); } |
In this case, the action method receives an input argument that has been properly processed by the ASP.NET MVC model binder object. (The model binder is an ASP.NET subsystem that maps query string values, form values and specific URL tokens to declared parameters of the action method. The match occurs on the name of the parameters.) Any input for the controller method is, one can reasonably assume, a good input for the orchestrator too. In addition, the controller can add some more data from intrinsic objects. In the preceding code, the action method receives a date and is expected to generate a view in which all news fresher than the specified date are listed. However, the use-case also requires that articles are in some way personalized and filtered by the preferences for the currently logged user. The controller selects the name of the current user from the User intrinsic object and passes that information to the orchestrator.
Inside an Orchestrator Object
An orchestrator class is a collection of methods that return a view model object-data as being worked on in the next view-and accept data taken from the request URLs or the HTTP context. Each method performs a number of operations as required by the use-case that the orchestrator addresses. This logic can be as simple as plain CRUD or, on the other hand, it could be so complex as to require a workflow and orchestration of external services, domain services, data repositories and whatever else you may have in the application’s backend.
The orchestrator object receives and returns data as the presentation layer expects it, but internally it may need to deal with data using a different representation, the domain model. The M in MVC, model, is merely about the data the view works on. This is not necessarily the same model you use to manipulate the data in the business layer.
If your application is entirely built using the Microsoft stack, then you may have an entity object model to persist to some database via Entity Framework. This means that the orchestrator will either call directly into the Entity Framework object context or use the services of a repository class. Along the way, the orchestrator may need to sync up with other Web and WCF services, external applications and ad hoc modules.
Most of the time, testing the orchestrator is vital for the development of the application. For this to happen, you should endeavor to make any dependency injectable on repositories, services, and whatever other module it is required.
In situations in which the orchestrator is simple enough to only require a database operation, whether it’s a query or update, you may even decide not to test it with unit tests, but to proceed directly with integration tests.
Finalizing Controller Methods
When you use orchestrators in a very simple use-case, basically a plain CRUD with no extra logic around, then you may find yourself dealing with a very basic implementation of controller methods:
1 2 3 4 5 |
public ActionResult Index() { var model = _orchestrator.GetHomePage(); return View(model); } |
In the real world, even in this simple case you will likely need more code around for things like exception handling and preconditions. Exception handling is often resolved via attributes on the method; preconditions can be effectively placed via contracts, as below:
1 2 3 4 5 6 7 8 9 10 11 12 |
[SomeException] [SomeOtherException] public ActionResult GetNews(DateTime startDate) { // This check here is just an extra: this check belongs // especially to the domain model. Contract.Requires<ArgumentException>(startTime.Year >= 2010); var user = User.Identity.Name; var model = _orchestrator.FindNews(user, startDate); return View(model); } |
Exception handling can be implemented by a direct try/catch block, but attributes contribute to keep the entire method clearer to read. The same can be said for preconditions. Preconditions on a method are essentially a sequence of if-then-throw statements placed at the beginning of the method. Using contracts in .NET Framework 4 is functionally equivalent and enables your code to work with static analysis tools.
Summary
Even though ASP.NET MVC is sometimes perceived as the framework that allows you to write better code, that’s not necessarily true. The controller class is only a more pattern-friendly version of the old faithful code-behind class. Writing clean ASP.NET MVC code is still up to you!
Load comments