September 4, 2011

Finding the balance of abstraction - From web forms to MVC

Before ASP.NET MVC I am ashamed to admit that I didn’t actually know how to create a normal html form. I had a fairly good understanding of html, but my first taste of dynamic web sites was with ASP.NET web forms. This meant that I was far more accustomed to writing:

<asp:textbox runat="server" id="txtName"/>

than

<input type="text" id="txtName" name="Name"/> 

This layer of abstraction that web forms “provided” was supposed to make it easier for developers more used to a “draggy-droppy” type development experience. With web sites becoming more interactive via client-side frameworks such as jQuery, this abstraction started to become a real pain in the arse. In fact, before I started working with ASP.NET MVC I hated writing javascript. Nowadays, I do it daily, and avoid using web forms like the plague.

One great thing about ASP.NET MVC is that it’s far easier to learn for developers of other frameworks such as PHP and Ruby on Rails, especially with the release of the Razor templating syntax.

Since the first release of ASP.NET MVC we have had html helpers. These are little nuggets of code that make it easier to create views (the V in MVC in case you didn’t know already).

For example, here’s a snippet from one of our admin forms:

<div class="editor-label">
	@Html.LabelFor(model => model.PublishDate)
</div>
<div class="editor-field">
	@Html.EditorFor(model => model.PublishDate)
	@Html.ValidationMessageFor(model => model.PublishDate)
	@Html.HintFor(model => model.PublishDate)
</div>

Here we are using some of the built in helpers (LabelFor, EditorFor, ValidationMessageFor) and a custom one (HintFor). The real advantage of these helpers is that they are strongly typed to our view model.

The EditorFor helper is particularly special in that it renders the appropriate input element based on the data type of the view model property (i.e. DateTime). The built in form helpers also wire up client-side validation automatically (using jQuery validation) which saves a bunch of work.

I’m all for these type of form/input helpers. Where it can start to go wrong is when helpers are used for presentation. A prime example is MvcContrib’s grid helper:

<%= Html.Grid(Model.People).Columns(column => {
     		column.For(x => x.Id).Named("Person ID");
     		column.For(x => x.Name);
     		column.For(x => x.DateOfBirth).Format("{0:d}");
     	})
        .Attributes(style => "width:100%")
     	.Empty("There are no people.")
     	.RowStart(row => "<tr foo='bar'>") %>

Now for someone who has never used this helper before this will look a little strange. Compare this to the equivalent Razor and HTML:

@if (people.Count == 0) {
	<p>There are no people.</p>
} else {
	<table style="width:100%">
		<thead>
			<tr>
				<th>Person ID</th>
				<th>Name</th>
				<th>Date of Birth</th>
			</tr>
		<thead>
		<tbody>
			@foreach (var person in people) {
				<tr>
					<td>@person.Id</td>
					<td>@person.Name</td>
					<td>@person.DateOfBirth.ToString("{0:d}")</td>
				<tr>
			}
		</tbody>
	</table>
}

By trying to save a few lines of code (using the helper) we’ve actually added a lot of complexity. If instead we embrace HTML we make our intentions much more clear and produce code that even someone unfamiliar with ASP.NET could understand. Let’s say you want to give this table some jQuery love. With the helper we’re back to web forms since we don’t have direct control over the html output.

Many people go on about not having any logic in the views. However, if you are correctly using view specific models, then I don’t see the problem with having a few “if” statements. People can understand these. It’s okay!

As an example, consider the following view model class:

public class ProductListModel {
	public IEnumerable<ProductModel> Products {get;set;}
}

In our view we want to display a list of products that are on offer, and a list of those that are not:

<h3>Offers</h3>
<ul>
	foreach (var product in Model.Products.Where(x=> x.OnOffer)) {
		<li>@product.Name</li>
	}
</ul>

<h3>Normal</h3>
<ul>
	foreach (var product in Model.Products.Where(x=> !x.OnOffer)) {
		<li>@product.Name</li>
	}
</ul>

Here there is no need for the filtering logic to be contained within our view. Instead we can extend our view model:

public class ProductListModel {
	public IEnumerable<ProductModel> Products {get;set;}
	
	public IEnumerable<ProductModel> ProductsOnOffer {
		return Products.Where(x=> x.OnOffer);
	}
	
	public IEnumerable<ProductModel> RegularProducts {
		return Products.Where(x=> !x.OnOffer);
	}
}

Our view becomes much more expressive and easier to understand:

<h3>Offers</h3>
<ul>
	foreach (var product in Model.ProductsOnOffer) {
		<li>@product.Name</li>
	}
</ul>

<h3>Normal</h3>
<ul>
	foreach (var product in Model.RegularProducts) {
		<li>@product.Name</li>
	}
</ul>

Here we’ve left the html (remember, people CAN handle HTML) but removed the linq lambda craziness that may look strange to non .net developers.

Finally, abstract application specific elements that the target user is unlikely to know, or that may break things if they change.

A good example of this is when producing links or calls to RenderAction. Below is a project details link taken from a fabrik theme:

<a href="@Url.Action("Details", "Project", new { id = project.Slug })" title="@project.Title">
	@project.Title
</a>

Note that I’m not using the built in Html.ActionLink helper. To me this is unnecessary abstraction. People understand a simple anchor tag. Not so many will know what an ActionLink is.

Imagine we have 20 themes, all using similar code to produce project detail links. What happens if we change our routing configuration or the name of our actions? It also requires that people know how to use the Url.Action helper.

Instead we created our own logically structured view helpers for generating urls. The above example can be rewritten as:

<a href="@fabrik.Portfolio.ProjectDetailsUrl(project.Slug)" title="@project.Title">
	@project.Title
</a>

Again this is more expressive and means that our theme developers do not need to understand the internal workings of our application. If using Visual Studio or Visual Web Developer Express, they will also get intellisense, and everyone loves that!

In summary I’ve provided some guidelines for producing ASP.NET MVC applications that may be contributed to by people of varying skill sets and ability.

1. Identify your audience

If your application is developed by an internal team of developers who are all ASP.NET MVC wizards then go nuts. This doesn’t mean you should pollute your views but it does mean you can assume knowledge of built-in MVC helpers.

If however you intend to support non .net developers then try and keep things simple. By not abstracting HTML unnecessarily and providing a well documented api, your UI developers can get by with little or no knowledge of .net. This is particularly important when creating themeable web applications where theme developers may only know HTML, CSS and Javascript. WordPress is a great example of this. Even I can produce a WordPress theme without knowing PHP!

2. Does saving a few characters add unnecessary complexity?

Developers are notoriously lazy. It’s very tempting to write a bunch of “helper” methods to save a few characters when producing a UI. However, does this come at a cost to other developers, especially those with no .net experience?

Note that this is closely related to your audience. For example we always use the built in form/input helpers for our admin views. However, these are only developed by ASP.NET MVC developers and since they save so much work it really is a no brainer.

3. Don’t assume application knowledge. Abstract where necessary.

Create helpers for elements that would otherwise require knowledge of your application internals. As previously mentioned, links to your action methods are a great candidate for this. It will also save you a lot of pain if you decide to change things since you have just a few helpers to update.

Document, document, document

There’s nothing more frustrating that seeing a great tool in action only to find that there is no documentation. You don’t need to document the common stuff (HTML, CSS, Javascript, Razor syntax), there’s plenty of resources for them. Instead document your presentation API clearly and if possible provide video tutorials of end-to-end examples (I find videos are the most effective learning tool - thank you TekPub).

Got anything to add? Drop me a comment below.

© 2022 Ben Foster