How we do MVC
Our team has been using the ASP.NET MVC framework on a production application for about 9 months now, and in that time, we’ve crafted a number of opinions on how we want to design MVC applications. Much of this design is inspired by the concepts from FubuMVC, and in particular, Jeremy’s post on their MVC opinions. Our approach is rather similar, but deviates in a few places.
I’ll elaborate on many of these concepts in future posts, but for now, in no particular order, our opinions:
- Controllers are thin and light, only responsible for communicating an action result back to the MVC pipeline. All major request-handling belongs in application-level services, that communicate operation results back up to the UI layer (controller), that then decides what to do with that result. In many cases, the “service layer” is a simple repository call.
- Strongly-typed views, and discouraging the dictionary part of ViewData. There are edge cases where we have to use it, but we try very hard not to use magic dictionary keys. It usually crops up around orthogonal view concerns, where we don’t want to pollute our “main” ViewModel with that information.
- Distinct ViewModels separate from the domain. Our entities aren’t built well for binding, whether it’s in a ViewModel (read-only) that throws NullReferenceExceptions in a “Something.Other.Foo.Bar” call, or in an EditModel (form) that uses a ModelBinder with very specific design requirements. The View puts very real requirements on your ViewModel. For many classes of applications, this is acceptable. For others, with more complex domains, this influence is unwanted. Creating a separated ViewModel provides a clean separation of concerns between View and Domain.
- No magic strings. None of this Html.TextBox(“Surprise.I.Am.Actually.A.Property”) business, expression-based syntax for everything that refers to a 1) class 2) method or 3) property. This means:
- Using expression-based form generation (Html.InputFor(order => order.Customer.Name)
- Using expression-based URL generation (Url.Action
(c => c.Index()))
- Using expression-based RedirectToAction methods, similar to Url.Action
I can’t stress enough that last point. If you have a string referring to a member on a type, use expressions! String-based reflections leads to subtle runtime errors, where refactoring screws up your views and controllers.
- Smart model binding. Our model binders can bind:
- Validation on your EditModel. We use Castle Validators on our EditModel to do the rote “OMG this is totally not a number type lol” validation, range validation, required field validation, and so on. Our validation occurs inside our model binder, before our action method gets called. This isn’t done in any filter or anything like that. We had to jump through a few hoops to merge the two concepts, as you have to match up Castle’s error summary with MVC’s concept of Model State, as well as taking care of nested levels of access (that Customer.Name example).
- AutoMapper to go from Domain –> ViewModel and Domain –> EditModel. This is again because the view and controller put constraints on our model that we didn’t want in our domain. AutoMapper flattened our domain into very discrete ViewModel objects, containing only the data for our view, and only in the shape we want.
- Very smart HTML generation. We have exactly 1 method call for generating a form element, “Html.InputFor”. As part of that “InputFor”, it examines an input specification, that collects the PropertyInfo, any attributes, the type, any modifiers called, and selects an appropriate InputBuilder. Call InputFor(p => p.Id) and Id is a GUID? That creates a hidden input element. Call InputFor(p => p.Customer.Address) and Address is a complex type? That looks for a partial with the same name of the type. With all InputFor’s expression based and going through the exact same method, we can:
- Create classes of inputs, such as radio buttons for enumerations, drop-downs for enumerations, date-time pickers for DateTimes, and so on
- Automatically include standardized output for label elements and error information
- Extend new input builders for specialized cases (plugged in through our IoC container), which matches based on a simple IsMatch(inputSpecification)
- Standardized look and feel for all classes of inputs
We originally extended HtmlHelper for every new thing, but it got confusing what the right builder for what situation was. Instead, we used exactly one method and stuck to easily discoverable modifiers (InputFor().AsDropDown()) or metadata on our EditModel (RequiredFieldAttribute automatically outputs a little asterisk next to the input). It got rid of TONS of duplicated logic, and we had lots of flexibility in the granularity of our input elements, from a single textbox to a complex form that used a partial. The partials were especially nice to be tagged based on type, as things like an AddressForm required no new View work. We just made sure our EditModel had an AddressForm as its type for any address (which is filled in by AutoMapper).
- Standardized action method names. RESTful, but not REST, which we don’t need in our context.
- UI testing with WatiN and Gallio. WatiN executes through Gallio, which is parallelizable (our quad-core build server executes 4 test classes, and therefore browsers at a time). We use Gallio’s concept of test steps to output meaningful information about our tests. We transform the Gallio output into an HTML report, which becomes part of our deliverables. People paying us for our services want assurances that our application works, and want to know in a nice pretty way. When a test fails, WatiN takes a screenshot, and we attach it to the failing test, which then shows up in our build report. Let me repeat: our automated UI testing includes screenshots of failures, all in the background, with no human intervention.
- UI tests use our ViewModel and EditModel types, along with expressions, to locate and validate elements. We do things like ForSection
.LocateRowWith(p => p.ProductName, “SomeName”).CheckValue(p =>p.Total, 15.6m). It’s all strongly-typed, and refactoring-friendly. All ViewModel data is wrapped in SPAN tags with predictable CLASS attributes based on the expression. All INPUT element IDs and NAMEs are generated from the expression as well. WatiN uses the exact same mechanism to generate a string name, ID or class from an expression, resulting in compile-safe UI testing. When we delete a field from a view, we remove it from our ViewModel, so our UI test won’t even compile. It’s all very refactoring-friendly.
- Actions receiving a POST receive the EditModel as an action parameter, not some collection object. This isn’t ASP 3.0 anymore, we don’t need Request.Form.
- Use partials when you have common markup, and the data is in your top-level ViewModel object.
- Use RenderAction when you have common markup, but the information is orthogonal to the main concern of your view. Think like the “login widget” at the top of every screen. A filter is too much indirection for that scenario, RenderAction is very explicit.
- No behavior in filter attributes. Attributes are for Metadata, not Metabehavior. Delegate the real work of the filter attribute to a filter class. You can’t control the instantiation of a filter attribute, and that gets annoying if you want do to dependency injection.
Like many of the frameworks coming out of Redmond, MVC is not an opinionated framework. Rather, it is a framework that enables you to form your own opinions (good or bad). It’s taken quite a long way, with a very stable result at the end. It’s certainly not perfect, and there are a few directions we’d like to go differently given the chance. In the next few posts, I’ll elaborate with real examples on the big examples here, otherwise, I’d love to have people poke holes in our approach.