Theme by nostrich (modified).

Mon Jan 19 2010 08:22:52 GMT

Why ASP.NET MVC 2 is Broken

NOTE: I'm happy to say that the majority of these issues are fixed in the ASP.NET MVC 2 RTW release. I've now been using the framework in a production app for a number of months and am very happy with it - kudos to the ASP.NET MVC team for listening to community feedback! I'm keeping this post here for posterity.

I'm a big fan of the ASP.NET MVC framework. I've been using it at work since early 2008 (around preview 2) so I've become quite used to it. I particularly like that the extension points that have been opened up allow you to mould the framework to the way you want to work, rather than the other way around (*cough** WebForms *cough*).

ASP.NET MVC 2 has been out in Preview and Beta form for a while now. In fact the Release Candidate has recently been released, which means the full 'RTW' release is just around the corner. Which is a pity.

It's a pity because out of all of the new features of ASP.NET MVC 2 none of them are well-baked enough for me to justify upgrading to (yet I'll probably be have to). Why? Let me show you, one feature at a time.

Better Validation

MVC 2 features built-in support for model-level validation (via DataAnnotations attributes) and Out-Of-The-Box support for client-side validation with jQuery. It sounds quite nifty, you add DataAnnotations attributes to your ViewModel properties like this:

public class FooModel
{
   
[Required]
   
public string Name { get; set; }
}

If you submit the form for this Model without the Name field completed, then you'll see in your Controller Action that the ModelState object automatically gets filled in with a model error saying that the Name field is required. Great!

Not so much when you start to dig into it. Say you have ~20 fields to add to your View and you forget to create one for Name. That's not big problem because the validation will catch it right? It is a required field after all. Unfortunately you'd be wrong to assume this, because validation is only run on values that are posted back to the server, and if you don't have a field on your View, then no value is posted back to the server for that fieldName, and so DataAnnotations won't even check it against your attributes.

Ah but that's just the built-in Validator, if I plug in another validation framework then I can fix this behaviour.. Yes MVC 2 is extensible enough to hook in your validation framework of choice (Castle, NHibernate Validator, Fluent Validation etc). Unfortunately Jeremy Skinner recently blogged about his experience trying to create a Provider for Fluent Validation and points out a number of issues with this, one very closely related to my point above.

Update: Ayende has chimed in, pointing out that this behaviour violates the principle of least surprise. I completely agree.

What can I use instead? SharpArch and xVal wrap other validation frameworks to provide similar functionality. I've used both NHibernate Validator and Castle validation frameworks and their more or less comparable in terms of features (both FAR more powerful than DataAnnotations).

Strongly-typed HTML helper methods

The new Html.EditorFor and Html.DisplayFor helper methods allow easily templated views & a nice, clean, strongly-typed lambda syntax.

<%@ Page Inherits="System.Web.Mvc.UI.ViewPage<FooModel>" %>

<% using(Html.BeginForm<FooController>(c => c.Submit(null))){ %>
<fieldset>
<%=Html.EditorFor(m => m.Name)%>
</fieldset>
<% } %>

This View will render (by default):

<form action="/Foo/Submit" method="post">
<fieldset>
   
<div class="input-label">
       
<label for="Name">Name</label>
   
</div>
   
<div class="input-field">
       
<input type="text" name="Name" id="Name" />
   
</div>
</fieldset>
</form>

Look at that! Very nice: no magic strings and refactor-friendly. There's even a Html.EditorForModel helper that will take your whole model and spit out inputs for every field. There are sensible defaults for different field types (string => text, bool => checkbox etc), but you can customise this with the DataAnnotations UIHint attribute. Fantastic, no?

The problem with this comes when you need to change the HTML that gets outputted. For some reason the input-label and input-field divs are hard-coded into the default template, with no way to override globally. Yes you can override them, but then you need to remember to pass the template name around in all your views, not nice. See correction below

But the biggest problem I have is that these strongly-typed helpers don't support arrays. Say you have another model that looks like this:

public class BarModel
{
   
public FooModel[] Foos { get; set; }
}

And then in the Bar View you have a call to Html.DisplayFor(m => m.Foos[0].Name). You'd expect the input field rendered to be named Foos[0].Name, which would work with the DefaultModelBinder, but it isn't. Instead you'll get a field named Name, completely ignoring the Foos part. This basically breaks this feature for everything but the most basic scenarios.

Correction: I originally said that Html.DisplayFor(m => m.Foo.Name) doesn't generate a correct field name. While this was correct for Beta 2, it appears to be fixed for the RC. Arrays are still broken though.

Correction 2: Brad Wilson has chimed in in the comments to say that array support will be in RTM (despite the codeplex issue indicating otherwise), and to point out that MasterPages can be used to change the surrounding HTML. I highly recommend reading his blog series on MVC 2 Templates for more information.

What can I use instead? MvcContrib has had its own InputBuilder sub-project for a while now. This uses MasterPages for changing the surrounding HTML so you can completely change the layout of your templated fields, plus it properly supports submodels, and actually converts CamelCase property names such as EmailAddress into more readable text such as Email address.

Areas

This is one thing that was sorely missing in MVC 1.0 (so much so I felt like writing a rant about that broken feature back in the day). Areas allow you to break a complex application into multiple pieces & can really help when working on a project across a decent size team.

I should admit to a slight lie here - the Area support in MVC2 works quite well. Sure the tooling isn't great as there are multiple different view locations you can use for your areas (~/Areas/XX/Views or ~/Views/Areas/XX/Views) and Go to View doesn't seem that intelligent in finding the right one, but the actual infrastructure around Areas appears to be quite robust.

The problem, then, comes when you try to use my example above. See the call to Html.BeginForm? That's the problem. The lambda-based methods in MvcFutures simply don't support areas, and Microsoft have no plans to fix them, despite a basic 80-90% working fix being quite easy to make they have opted not to. This basically makes the lambda-based extensions completely useless if you're starting a project that might even possibly use Areas in the future.

What should I use instead? You could go back to using the non-generic version of Html.ActionLink and Html.BeginForm of course, but then I'd rather stick hot pins in my eyes. MS recommends T4MVC, which will generate strongly-typed classes for your controller methods, it looks quite nice but I inherently dis-trust T4 due to the IDE lock-in and that you need to install an VS plugin just to be able to edit the templates in a nice way. Then you're more or less out of luck unless you fork MvcFutures and make the necessary modifications yourself.

So should I not use MVC 2 at all?

Unfortunately you probably don't have much choice, the various frameworks such as MvcContrib are already releasing against MVC2, and others will follow suit eventually. The additional benefits that MS list such as performance gains will probably be worth it too.

Summary

I think Microsoft have rushed MVC 2 out of the door too quickly, and have not let themselves enough time to incorporate community feedback into the product. Their opposition to accepting patches to the project is also hurting their progress in this regard. MvcContrib can only go so far as they have no infuence over the core design of the product.


Tagged: aspnetmvcaspnetmvcmicrosoft

View the discussion thread.blog comments powered byDisqus