Our “Opinions” on the ASP.NET MVC (Introducing the Thunderdome Principle)

I’ve got my asbestos underwear on, so flame away…

 

So many of us are so thrilled at seeing an official Front Controller / MVC framework for ASP.NET development that I think we’ve forgotten to ask ourselves “Is this thing any good?”  In *my* opinion, if you look at the ASP.NET MVC framework as a complete framework to be used out of the box, the MVC framework isn’t very good at all.  However, as a set of fairly loosely coupled classes that you can use to build a highly productive, specialized MVC stack for your specific application needs, the ASP.NET MVC framework is perfectly fine.

Basically, my issue with the MVC framework is that I think it falls far short of Ruby on Rails in terms of productivity enhancing features and testability.  Specifically, I think the MVC framework – out of the box – has failed by not embracing the Rails mantras of Opinionated Software, Convention over Configuration, the  DRY Principle, and Testability support.  That’s okay though, because we’ve found it relatively simple to cherry pick the pieces of the MVC that we wanted (routing, some of the ActionResult classes, and the WebFormsEngine) to form the core of the MVC framework that we wanted.  After deciding that we would use the MVC framework (why we chose the MVC over Rails and MonoRail is a different discussion), Chad & I immediately proceeded to develop a set of “opinions” on how we wanted our architecture to work and built up some infrastructure around the MVC to make these “opinions” become true.  We’re doing a joint presentation at KaizenConf called Using and Abusing ASP.NET for Fun and Profit that’s all about our particular usage of the ASP.NET MVC framework.  As a preview of that talk, and for those of you who won’t be in Austin for KaizenConf, here are the “opinions” that we’ve adopted.

Our guiding principles have been heavily influenced by Rails.  We’re using Convention over Configuration as much as possible, and we’re adamant about DRY.  We’ve deviated from Rails by trying to use the strengths of C# 3.0 rather than trying to make C# act like a half-assed version of Ruby.  To that end, we very heavily use Expression’s, Lambda’s, Object Initializers, and Generics.

Opinions

  • The “Thunderdome Principle” – All Controller methods take in one ViewModel object (or zero objects in some cases) and return a single ViewModel object (one object enters, one object leaves).  The Controller classes will NEVER be directly exposed to anything related to HttpContext.  Nothing makes me cry like seeing people trying to write tests that mock or stub that new IHttpContextWrapper interface.  Likewise, Controller methods do not return ViewResult objects and are generally decoupled from all MVC infrastructure.  We adopted this strategy very early on as a way to make Controller testing simpler mechanically.  It’s definitely achieved that goal, but it’s also made the Controller code very streamlined and easy to read.  We’ll explain how this works at KaizenConf.
  • Controllers should have no knowledge of the View.  View’s should have no knowledge of the Controller.  They only share the Model class, period.
  • Controllers should be thin.  The only job of a Controller action is to translate the model coming in to the proper service calls and to create the output model object.  All responsibilities for business logic are done by delegating to non-UI service classes.  In other words, business logic does NOT go into a Controller.
  • No slinging around HashTable’s, IDictionary’s, or magic strings.  All of these things are error prone and make development harder than strongly typed parameter objects.  Instead, we…
    • …say that all navigation is done by an Expression like:  <%= this.LinkToAction<HomeController>(c=>c.Index(), UserMessageKeys.MAIN_HEADER_TEXT) %>.  The actual implementation is half convention, and half configuration of controller “aliases.”  This choice does not lock you into making all url strings an exact representation of the controller name and method name.
    • …as I said earlier, all Controller actions are invoked by passing in a single parameter object
    • …allow no direct access to HttpContext collections.  In the very rare case we need to access something in HttpContext, we use a wrapper interface that expresses that need in terms of our application.  Dependency Injection is used to get the HttpContext wrappers to the Controller classes that need them.  See this post from Jimmy Bogard for another example.
  • All View’s are strongly typed View’s (ViewPage<T>, where T is the ViewModel that comes out of a Controller action).  This is somewhat necessary because of the Thunderdome principle, and also enables the bullet point below…
  • All html form elements are built by Expressions with our own html helpers like this:  <%= this.TextBoxFor(m => m.Site.Identifier)%>.  This has worked out very well because:
    • It makes screen synchronization simpler by binding the element names to the actual properties of the Model object.  With a little code generation, we’ve basically made the effort to synchronize data between the view, the Request.Form, and the internal Domain Model objects a miniscule part of the development effort.
    • This cuts down on mistakes from misnamed elements or mangled string key values on the server side
    • It’s enabled automated testing of the screens.  We can address screen elements in tests by a strong typed Expression as:  DropDownFor(x => x.Site.Status).HasOptionValues(Given.StatusList);
    • We have access to the underlying PropertyInfo while we’re building textbox’s and dropdown’s and the like.  We’re just starting to take advantage of this to intelligently set restrictions on what type’s are valid in the html elements.  We’re also tying into our validation attributes to automatically mark fields that are required and set some client side validation rules on the html elements.  Sooner or later, we’ll also tie in tooltip values into the html controls.  And all of this comes without adding any more code on the actual View. 
  • Server side markup is never intermingled with client side JavaScript.  It is our opinion that this all too common technique leads to unreadable code and eliminates the ability to TDD the client side JavaScript.  This:  callFunction(‘<%=Model.Variable%>’) is not allowed.  If server side data needs to be passed to client side JavaScript, we do that by writing “var something = <% =Model.Variable%>.” 
  • View’s should be very simple.  If you’re using an if/then statement or some sort of looping expression, you’re doing something wrong.  Conditional logic belongs in the Controller or in JavaScript libraries that can be tested with QUnit.  No, or minimal, logic in the View’s reduces errors by moving logic out of hard to test code and into easier to test code – and yes, I’m declaring that JavaScript is easy to unit test.  Tag Soup can be avoided.  We tend to move looping constructs into our own partial implementation like:  <%= this.RenderPartialForEachOf(m => m.Solution.Resolutions).Using<EditResolution>()%>.  In that block of code, EditResolution refers to an ASCX control, and m.Solutution.Resolutions is a property of type IList<Resolution>.  This statement will iterate over the list, and render a partial view for each Resolution object.
  • All Domain Model classes are identified by a single numeric property called “Id” (enforced with a layer supertype).  Nothing uncommon about that opinion, but it does eliminate repetition in the code by allowing us to reuse a lot of code for finding objects and Url routing between different types of Domain Model objects. 

 

Other Stuff We’re Doing

  • We use QUnit quite extensively and I’m thrilled with it as a tool.  We try to treat our JavaScript work as “real” coding and apply the same quality requirements to JavaScript as our C# code, and that means TDD.  We’ve found that many things are easier to unit test and build with jQuery / QUnit rather than server side markup and NUnit.
  • We do have a way to test View’s in isolation with WatiN.  We can set up the input Model class in memory, then render that Model with a View and make assertions on how the View was rendered.  Rails has support for this very thing, and I’ve always wanted this ability.  This hasn’t been as useful as I thought it was going to be once we adopted the rules about simplicity in the View’s and started using the QUnit / jQuery combination.  In order to make this ViewContext work, however, we had to forgo all usage of the built in HtmlHelper’s because of some unfortunate coupling to HttpContext.
  • We’re using Fluent NHibernate for configuration.  We’re about to remove some of the explicit configuration in favor of the convention based mapping.  At this point, we’re completely generating the database with NHibernate’s HBM2DDL tooling.  The database is just flowing out of the Domain Model for the time being (until we have a need to optimize the default DDL).  We have added some conventions to Fluent NHibernate to tie our validation attributes into the DDL generation.  DRY baby.  The lesson we can take out of Rails success is DRY.  It’s just that the shape of DRY code is a bit different in the .Net world.
  • We have a custom BDD style “Context” abstract class for our Controller actions.  Because of the “Thunderdome Principle,” it’s simply a matter of defining the input model, running the method, then doing state-based assertions against the output model.  No HttpContext.Request.Form setup, and no scraping data out of a weakly typed ViewResult after the fact.  This “Context” class also uses the StructureMap AutoMocker behind the scenes to cut down on the overhead of setting up tests.  Here’s an example of that context in action testing a service class:

[TestFixture]

public class When_yanking_a_workflow_item :

    ActionContext<WorkflowService, YankRequest, WorkflowActionResult>

{

    private User theCurrentUser;

    private Queue theOriginalQueue;

 

                                                  // Defines the method

    public When_yanking_a_workflow_item() : base( (x, input) => x.Yank(input))

    {

        UseInMemoryRepository = true;

    }

 

    // Set up the context

    protected override void beforeEach()

    {

        theCurrentUser = new User().WithId(4);

        WorkflowItem theCase = new Case().WithId(1);

        theOriginalQueue = new Queue{Name=“OriginalQ”}.WithId(5);

        theCase.Enqueue(theOriginalQueue);

 

        // Setup the input model

        Given = new YankRequest

                    {

                        CurrentUser = this.theCurrentUser,

                        Item = theCase

                    };

    }

 

    [Test]

    public void the_owner_should_be_changed_to_the_current_user()

    {

        // The first access of the “Output” property

        // runs the Controller action and caches the return

        // value of the Controller action

        Output.Item.Owner.ShouldBeTheSameAs(Given.CurrentUser);

    }

 

    [Test]

    public void it_should_no_longer_be_assigned_to_a_queue()

    {

        Output.Item.Queue.ShouldBeNull();

    }

 

    [Test]

    public void the_item_and_the_queue_and_the_log_should_be_saved()

    {

        TheLastTransactionWasCommitted()

            .TheSavedObjectsWere(Output.Item, theOriginalQueue, Output.Item.LastAction);

    }

}

 

In Closing

Please don’t think that I’m saying that the ASP.NET MVC is bad, because it’s not.  I just think that the MVC out of the box is too “bland” and wishy washy.  If you’re going to use it for anything bigger than a handful of screens, I’d highly advise you to add some of your own application specific sauce.  I think we can agree that Microsoft couldn’t make the MVC framework opinionated without pissing off 3/4’s of its users (just think, if they were to build in direct support for persistence they’d have to use Linq to SQL or the messy tool that shall not be named, neither of which I would consider to be acceptable).  Making the MVC opinionated, and hence more productive, is going to be left to the community and to each organization.

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in Design Patterns. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.goodcoresoft.com/asp-dot-net/ ASP.NET Development

    Well, ASP.NET MVC 4 is a framework for building scalable, standards-based web applications using well-established design patterns and the power of ASP.NET and the .NET Framework. ASP.NET developers

  • http://fritzhayden.webstarts.com/ FritZhayden

    Since VB.NET is an Object Oriented Programming language, its easy to find derive correct solutions to the application related problems.

  • http://dawnrocha.wordpress.com/ Frances Johns

    All of these things are error prone and make development harder than strongly typed parameter objects.

  • http://dorianward.webs.com/ AlfonsoRush

    ASP.NET MVC is an architecture to develop ASP.NET web applications in a different manner than the traditional ASP.NET web development.

  • SladeMeadows

    ASP.NET MVC framework as a complete framework to be used out of the box, the MVC framework isn’t very good at all.

  • http://www.keystrokesrecorder.org keyboard tracking software

     this comments very nice

  • Dan Turner

    How does one deal with redirect scenarios with the thunderdome principal?

    Specifically, lets say I want to combine the thunderdome principal with the PRG pattern.

    public ?? Create(NewThing thing){
            if(!ModelState.IsValid(thing))
            {
                   return thing;
            }
            //Do stuff with the valid thing
            //Redirect to the Thing Index view
    }

  • Admin

    Great job ! It’s a great theme. But like Andrew I would like to have the psd if you still have it (if you could just mail me, it would be nice

  • John Goode

    If you want Ruby on Rails, use it.

  • http://codebetter.com/members/jmiller/default.aspx Jeremy D. Miller

    @jayrdub,

    We heavily modified the ASP.Net MVC framework first, then ditched it altogether so that we could support this model.

    I think you could do some of it with MVC by using conventions to tie the view to a method’s return value.

    This was one of the issues that led Chad & I to move away from the MVC.

  • jayrdub

    I didn’t follow the part where your controllers “return a single ViewModel object” and “do not return ViewResult objects”. How do the views get ran?

  • http://codebetter.com/members/jmiller/default.aspx Jeremy D. Miller

    @E,

    We refer to the type of the view page. We simply use the namespace/full name of the user control to find the ascx. It does of course assume that the view’s namespace matches the folder structure.

  • E

    “EditResolution refers to an ASCX control”
    How do you refer an ASCX file with a class? Is this the model class, and with reflection you search for ViewUserControl and reach its ASCX?

    Thanks.

  • joecamel

    not to be the movie geek, but wasn’t Thunderdome, 2 enter – 1 leaves, not 1 enter – 1 leaves…

  • http://blog.zoolutions.se Mikael Henriksson

    >> Making the MVC opinionated, and hence more productive, is going to be left to the
    >> community and to each organization.

    Sounds reasonable to me!

  • http://graysmatter.codivation.com Justice~!

    I just had a chance to read this; kind of funny how you and I were basically both implementing some similar concepts at the same time yet separated by geography and thus having no idea the other was doing the same thing.

    “Two hearts, beating with just. One. Mind!”

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Steve,

    “I just implemented my own ModelBinder – this ‘shielded’ the controller from knowledge of the view/as well as no HttpContext required. (Because it was all mapped in the modelBinder).”

    Yep, and do the same on the back end. We have something else take the model coming out and set up the proper view with that model in the right place.

    “Let’s be real, I would suspect, and this isn’t a put down, that no matter what someone releases, you’d be incline to mold it/push it, etc… into something else.”

    Sure, and I think that’s generally a good strategy. The MVC is extremely generic and useful in lots of apps. We *can* wring out more productivity by making our own infrastructure on top, or around, or selectivly in place of the generic framework that is very specific to the application we’re building. We wouldn’t be as productive if we’d stayed inside the MVC idioms that you see online or in things like CodeCampServer. Yes, there was some nontrivial bootstrapping overhead, but I’ll still claim we’re better off.

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Stephen,

    Yes, we have our own base class for IController. That’s relatively easy to do. You can look at the MvcContrib for examples.

  • http://haacked.com/ Haacked

    @Joshua, if you read that post carefully, you notice he’s talking about apps, not frameworks. Now I know they would apply the same reasoning to frameworks as well, much less so.

    Taking a look at the Rails framework, it chooses the language Ruby.Ruby allows you to monkeypatch and pretty much override everything. So “opinions” (expressed as a deliberate constraint) in such a framework are like gossamer, easily set aside. An “opinion” expressed in an app is not easily set aside by a user.

    If we wanted to get extreme about our opinions (via constraints) we could have made every class and method sealed. I know for a fact that wouldn’t make Chad happy, as he’s pushed for quite the opposite. ;)

    Jeremy, you mentioned Rais as an example. I was curious how many Rails apps have you developed and deployed to production.

  • Bill English
  • http://anydiem.com Sean Scally

    >> why we chose the MVC over Rails and MonoRail is a different discussion)

    I’m interested in hearing what the arguments were, from your perspective, on that choice.

    I’ve picked MVC over Monorail for the reason that was pointed out earlier — ASPMVC is more like Merb, Monorail is more like Rails proper. My experimentation with Rails didn’t make me too happy because it seems to have thrown SOLID principles out the window in favor of RAD.

  • Steve

    “Controllers should have no knowledge of the View. View’s should have no knowledge of the Controller. They only share the Model class, period.”

    I just implemented my own ModelBinder – this ‘shielded’ the controller from knowledge of the view/as well as no HttpContext required. (Because it was all mapped in the modelBinder).

    So the controller only knows about this model object that is already been ‘bound’ from the form.

    Is that what you are referring to?

    Thanks

  • Steve

    ” I’m trying to say that there’s quite a bit of advantage in making an MVC stack that’s specifically optimized for the app at hand.”

    And you made one right? Each app is different, so as long as the framework allows for you to ‘make it fit for you’ then it’s a good framework.

    perfect. No complaints right? :)

    Let’s be real, I would suspect, and this isn’t a put down, that no matter what someone releases, you’d be incline to mold it/push it, etc… into something else.

    And from what I hear, that is what you did. Maybe when your down you’ll post your architecture on Codeplex so others can use it.

    Right?

    (I say this based on all your articles showing how you would do ‘x’ better – ie. CAB, now MVC, etc… I’m sure there are more)

  • Ben

    No slinging around a Hashtable’s what?

  • http://www.filip.duyck.org efdee

    Great post, two small remarks though:

    First, I think the Thunderdome principle is “two men enter, one man leaves”. “One man enters, one man leaves” would make for a pretty boring dome ;-)

    Second, don’t you contradict yourself by first saying that your Views don’t know about your Controllers, followed by showing an example of your View using a direct reference to a Controller and it’s method when using ActionLink ?

  • Stephen

    I like most of the points, although I’m not sure how pure you can really be where you were saying you can build on top of the current mvc to make it cleaner?

    Do you have your own implementation of an IController? or are you just ensuring you don’t explicitely use HttpContext thats accessible via the ControllerContext / RequestContext?

  • http://www.codinginstinct.com Torkel

    I would be interested to learn more on how you handle view testing in isolation using WatiN.

  • http://twitter.com/KeesDijk Kees Dijk

    Thanks Jeremy, love this article. I would realy like to see the possible recording and sources.

    Maybe you could put this in a screencast called “How to build a well designed MVC Application”, so after watching these screencasts http://www.asp.net/learn/mvc-videos/ people know the difference between making mvc apps work and designing mvc apps well.

    Just one small comment, when fighting in the thunderdome the people shout “two men enter, one man leaves.” http://en.wikipedia.org/wiki/Mad_Max_Beyond_Thunderdome

  • http://blog.lozanotek.com Javier Lozano

    @Jeremy,

    I need to check that out…what’s way cleaner.

    Another questions, how do you go about testing your views?

  • http://www.lostintangent.com Jonathan Carter

    @Jeremy

    Some of these are cited as examples of Rails CoC’ness in the article you linked…

    1) Controller/action-to-request mapping
    2) View/partial discovery
    3) HTML helper-to-viewdata mapping
    4) Model state
    5) DefaultModelBinder: automatic rehydration of complex objects
    6) DefaultValueProvider: automatic retrieval of action parameters for route/query string/form

    Don’t all of those allow a developer to assume the default behavior that ASP.NET MVC feels is conventional out-of-the-box?

    When I mentioned that you still have to configure lots of stuff, I was referring to things that ASP.NET MVC as a framework isn’t concerned with (doesn’t have an opinion).

    Rails no doubt provides a lot stronger CoC, but only due to the fact that it provides a much higher-level of opinions to the user.

    I’m not saying that ASP.NET MVC can’t get better (it’s only in beta!), but I think it’s done a good job of providing basic conventions for core behavior.

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Steve,

    ” I expect them to build generic frameworks that are highly extensible.”

    That’s kind of the argument I was trying to make. A “generic” framework is generally one that’s mediocre for lots of different applications. I’m trying to say that there’s quite a bit of advantage in making an MVC stack that’s specifically optimized for the app at hand.

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Jonathan,

    “you have to configure lots of things”

    That’s kind of what “Convention over Configuration” eliminates from your application. I’ll turn the question around on you, in what way do you think the MVC has embraced CoC?

  • http://www.lostintangent.com Jonathan Carter

    Great post Jeremy. You and Chad are definitely doing a lot of cool things.

    I’m curious though why it is you feel that ASP.NET MVC has failed at embracing convention over configuration? Going by the Rails mantra (COC = assuming defaults), I’d think that ASP.NET MVC has embraced that very strongly.

    I agree that due to its lack of higher level opinions, you have to configure lots of things, but that seems to go back to the issue of it not being as opinionated as many would like, not an issue with it embracing convention over configuration at all.

  • Steve

    Must be a good framework if you can make it do for you what you need done. I expect them to build generic frameworks that are highly extensible.

    Hats off to MS for a job well done so far!

    One question: you praise Ruby on Rails, do you have some examples you’ve done to show us ?

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Javier,

    It’s just a little sleight of hand. If we have something like that, it would be:

    < % =RenderPartialFor(x => x.PartOfTheViewModel).With () %>

    If the “PartOfTheViewModel” doesn’t exist, we simply don’t call through to the actual partial rendering.

  • http://blog.lozanotek.com Javier Lozano

    Great post, Jeremy! I love the “Thunderdome Principle”, great way on expressing the point in a way people can understand.

    I’m a little confused as how you avoid if/else’s in your view. Currently the only reason why I have an if/else in the view is to display “there is data” or “there is no data”…granted, I can have the controller return a different view with that information, but I felt that having two views was a bit too much overkill for what I’m doing. I’m really interested to see how you solved the problem since although my solution works, it is still pretty clunkly.

  • http://joshuaflanagan.lostechies.com Joshua Flanagan

    Haacked – I am not of the opinion that your example of an opinion is the same as my idea of an opinion when it comes to opinionated software. ;)
    https://gettingreal.37signals.com/ch04_Make_Opinionated_Software.php

    An opinion in “opinionated software” is a deliberate constraint. What you describe is called a “decision”. You decided to not have an opinion about what an action name represents.

    And I don’t think you have to be defensive of that decision when building a framework. The community, (or Microsoft, as a separate logical package), can build the opinionated stuff on top of the framework.

  • http://haacked.com/ Haacked

    Merb is one of the many influences on our thinking. In part, you can look at v1 of ASP.NET MVC as a Framework for building ASP.NET MVC Frameworks. I do think it’s complete enough out of the box for many, if not most, web apps.

    While we haven’t made it opinionated in the extreme (ViewPage and ViewPage comes to mind), I don’t think we’ve been extremely wishy washy. We do have our opinions and it shows in MVC. I think we just disagree on some of those opinions, which is fine, as that’s the whole point of opinionated software.

    Case in point, we made the strong opinion that actions are decoupled from methods. An action name does not necessarily represent the name of a method. Thus we don’t include strongly typed helper methods such as ActionLink. This has made a lot of people upset, true, but we’ve held to our opinion in this case.

    However, as you’ve pointed out, it’s very easy to build a stack on top of ASP.NET MVC such that ActionLink works because you won’t (ostensibly) do anything that would make it not work (such as tinker with certain extensibility points that would break that).

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Paul,

    We’re going to try to record as much as possible. I don’t know how well our session will translate to video since we’re going to go very informal, but we’ll try.

  • http://paulbatum.blogspot.com Paul Batum

    Really interesting stuff Jeremy. I am desperately hoping that your kaizenconf presentation (along with several others) will be recorded.

  • http://iqueryable.com Steve

    It’d be interesting if Microsoft (or the community) took the approach that Merb is taking. They make the core of Merb pretty bland, but they also have Merb Stack which takes all their opinions about the best way to build web apps and makes it very easy to install and use. This gives those who want to swap shit out the option to do so, while also making a stack that is opinionated and productive right from the start. Additionally allowing people to build their own “Stack” allows communities to form around particular approaches (“Stacks”) that are optimized for how those in that community work/think.

  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @Kevin,

    We’ve thought about doing that, but it won’t happen before next year. Gotta get permission first, which I’m confident that we will.

  • http://weblogs.asp.net/kdente Kevin Dente

    Nice. I agree that a more convention-based approach is hugely valuable, and will be up to the community to build on top of the base framework. Any plans to open-source any of those MVC pieces (beyond Fluent NHibernate, which I know is already open source)?