Wednesday, March 21, 2007

Why ASP.NET is leaky... Example of over-engineering

Ayende has been talking about leaky abstraction a lot in his recent posts about ASP.NET. He is especially hates a lot on the viewstate and postback handling on ASP.NET. This is one of them:
http://ayende.com/Blog/archive/2007/03/20/WebForms-and-lies.aspx

Be frank I have never develop something so heavy that I have to consider all the viewstate issues etc. However I do come across very much issue when I want to do dynamic control creation in asp.net....

But, this is not what I aim to talk about in this article. What I wanna share with you all is an aspect of web development myself has interpreted as a leisure developer on both asp.net and monorail, and what I had come across in asp.net makes me feel its leaky (and why monorail/ruby on rails is right).

Lack of Model Validation
Lets start with a simple example. Try to implement an online shop application using asp.net and monorail. When you want to do validation, asp.net tells you to put validators on the form. So you have validator in customers' billing address maintenance screen, as well as order checkout (when you allow user to ad-hoc override the address of the order). You end up have 2 validators doing exactly the same function. If you have shipping address as well, then it goes to 3 validators.

All a sudden your customer hated your program to enforce 'City' to be entered in address, so they want you to release the control on City. I end up going through all 3 pages, removing the validators, and as I am such a forgettful person, I forget one of them and I end up being kicked because of this issue.

In MR/RoR, ORM feature with validation is built in, so all you need to do is to add
[ValidateNonEmpty]
public string City {
...
}

This will trigger all form generator to apply mandatory validation, and everything is done with client side script. When my customer wants me to release the control, simply I just remove this attribute and everything are released. No hassle, no duplicate work.

Over-engineered Abstraction
I have my own term to describe this issue. As oppose to leaky abstraction described by Ayende, I call this 'over-engineering'. This is a good term I think as it describes the tendency of people try to over complicate simple matters very well.

A very good example is the 'need' of 10+ state events in a typical ASP page. Can you really think of a good reason why I need 10+ state events instead of just one? Personally I can hardly think of any. I have seen the entire asp.net model trying to do something too much like Windows. The fact so many people stated are, web, is designed to be stateless. Web has never getting more complicate than:

GET bugthis.blogspot.com
...
and return some HTML. The most complicated one would be a POST with some variables written to server, but thats all.

ASP.NET wants you to forget this, and learn 1000+ classes in System.Web to do your job. Lets use a very typical example in GridView. In ASP.NET, with the help of your pretty designer, you can create a grid very easy: just drag over and put it there. But- how can you, say, get 3 (note, not two) alternative styles rotated on the grid row, plus an Edit/Add/Delete link? You have couple choices:
1. Rewrite a control supporting 3 row styles
2. Use this everytime in RowDataBound (READ: EVERYTIME, EVERY GRID):
switch (e.Row.RowIndex % 3)
{
case 0:
e.Row.BackColor = Color.Red;
break;
case 1:
e.Row.BackColor = Color.Green;
break;
case 2:
e.Row.BackColor = Color.Blue;
break;
}
3. Give it up, the stock control will only support 2.

In Monorail, I write a component like this:
public class RepeaterComponent:ViewComponent
{
private string[] _styles;
private IEnumerable _collection;
private string _rowItemName;
public override void Initialize()
{
_styles = ((string)ComponentParams["styleClass"]).Split(';');
_rowItemName = (string) ComponentParams["rowItemName"];
_collection = (IEnumerable)ComponentParams["collection"];
}
public override void Render()
{
if (HasSection("header"))
RenderSection("header");
int currentStyleIndex=0;
foreach (object item in _collection)
{
PropertyBag[_rowItemName] = item;
PropertyBag["styleClass"] = _styles[currentStyleIndex];
RenderSection("row");
currentStyleIndex++;
if (currentStyleIndex == _styles.Length) currentStyleIndex = 0;

}
if (HasSection("footer")) RenderSection("footer");
}

public override bool SupportsSection(string name)
{
return name == "row" || name == "header" || name == "footer";
}

}

And then, I put this in my NVelocity view:
<style>
tr.red
{
background-color: red;
}
tr.green
{
background-color: green;
}
tr.blue
{
background-color: blue;
}
</style>

#blockcomponent (RepeaterComponent with "collection=$users" "rowItemName=user" "styleClass=red;green;blue")
#header
<table>
<tr>
<th>User Name</th>
</tr>
#end
#row
<tr class="$styleClass">
<td >
$user.UserName
</td>
</tr>
#end
#footer
</table>
#end
#end

You will say its more code, but within 30 minutes I got a repeater that does everything I need on rotating style, flexible layout (works on table, bullet list).

In ASP.NET, that routine will only help you when you have 3 colors, I think when you have multiple and decided to do something like my rotation style, its not going to be that simple (any brave challenger?) Whats more is you need a control that handles every repeater, even div sections.

Not Designer Friendly
You might still disagree with me on the previous part. But try to see the source of what ASP.NET generates:
<form name="form1" method="post" action="Default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUJNjc2NTE4NTMzD2QWAgIDD2QWAgIBDzwrAA0CAA8WBh4LXyFEYXRhQm91bmRnHglQYWdlQ291bnQCAR4LXyFJdGVtQ291bnQCBmQMFCsAAhYIHgROYW1lBQZWYWx1ZTEeCklzUmVhZE9ubHloHgRUeXBlGSsCHglEYXRhRmllbGQFBlZhbHVlMRYIHwMFBlZhbHVlMh8EaB8FGSsCHwYFBlZhbHVlMhYCZg9kFg4CAQ8PFgQeCUJhY2tDb2xvcgqNAR4EXyFTQgIIZBYEZg8PFgIeBFRleHQFB1dpbGxpYW1kZAIBDw8WAh8JBQVZZXVuZ2RkAgIPDxYEHwcKTx8IAghkFgRmDw8WAh8JBQdXaWxsaWFtZGQCAQ8PFgIfCQUFWWV1bmdkZAIDDw8WBB8HCiUfCAIIZBYEZg8PFgIfCQUHV2lsbGlhbWRkAgEPDxYCHwkFBVlldW5nZGQCBA8PFgQfBwqNAR8IAghkFgRmDw8WAh8JBQdXaWxsaWFtZGQCAQ8PFgIfCQUFWWV1bmdkZAIFDw8WBB8HCk8fCAIIZBYEZg8PFgIfCQUHV2lsbGlhbWRkAgEPDxYCHwkFBVlldW5nZGQCBg8PFgQfBwolHwgCCGQWBGYPDxYCHwkFB1dpbGxpYW1kZAIBDw8WAh8JBQVZZXVuZ2RkAgcPDxYCHgdWaXNpYmxlaGRkGAEFCUdyaWRWaWV3MQ9nZHLwlQri9Rg4hiZHDTVtYb0X/c7A" />
</div>

<div>
<div>

<table cellspacing="0" rules="all" border="1" id="GridView1" style="border-collapse:collapse;">
<tr>
<th scope="col">Value1</th><th scope="col">Value2</th>
</tr><tr style="background-color:Red;">
<td>William</td><td>Yeung</td>
</tr><tr style="background-color:Green;">
<td>William</td><td>Yeung</td>

</tr><tr style="background-color:Blue;">
<td>William</td><td>Yeung</td>
</tr><tr style="background-color:Red;">
<td>William</td><td>Yeung</td>
</tr><tr style="background-color:Green;">
<td>William</td><td>Yeung</td>

</tr><tr style="background-color:Blue;">
<td>William</td><td>Yeung</td>
</tr>
</table>
</div>

</div>
</form>

Why I got 2 divs and a form where I don't suppose to have any post activity? Simple. Thats ASP.NET's generation. You end up wasted bandwidth on _viewState and the useless tag, plus more importantly- your designer originally writes:
<style>
td.red
{
...
}
</style>

Now they have to take care the form and divs additions. Send that HTML to your designer and I am sure they will hate you a lot. :) Not to mention if you try to give them the .aspx, most people would not be able to modify them properly, thanks to the abstraction!

So, is asp.net a good approach? For people who never want to get involve with html/javascript, maybe. But for others who need the native power, I would suggest monorail.

ASP.NET is not alone. I feel a lot of so called 'modern' framework, including a lot of those in Java world, are heading the same direction. Thats why Ruby on Rails get so much attention and praise when finally a model with strict MVC architecture without trading off your raw HTML power and complicating things.

7 comments:

Anonymous said...

Good post! Your english is sometimes hard to follow, but your point is clear. You know what, the typical ASP.NET developer (like me) will have a hard time understanding your RoR example, though I can see it's pretty powerful. But in the end, ASP.NET viewstate does give me an easy time, after postback, all controls remain the same. You win some, you lose some.

William said...

This is not an RoR sample, instead its a MonoRail sample.

I agree what you said on win some and lose some on choices, the problem is this 'over-engineered' framework complicates the matter a lot. I think you could try to write something using MonoRail. Its not difficult to follow and I think you would get very inspired by the simplicity once you get your hands on it.

You can find MonoRail here:

http://www.castleproject.org

Marcel said...

Honestly, this example made me give up on any attempt to use MonoRail. I can't believe that you consider this an argument for it.

A few points:

1. You're actually writing a new component, exactly what you complain about in your point 1.

2. A new custom control derived from GridView that's supposed to rotate through 3 or more styles and add an Edit/Add/Delete link would be a lot simpler.

3. You forgot about the Edit / Add / Delete link, so your example would be even more complicated in reality.

4. Finally, if I understand this correctly, you haven't even tried to compile it - you use HasSection("footer") but declare a SupportsSection method.

I realize you're an enthusiast of the technology. I was actually trying to figure out how to make the damn thing work (it doesn't start with debugging, and without I just get back a 404 - even though I associated .rails with the aspnet_isapi.dll as described in the getting started guide. While I think that the whole thing is over-hyped, I admit that it would help... if it worked. Unfortunately, it doesn't, and this article doesn't help.

William said...

First, to be a real developer, you have to take the hard work and try to set it up. If you really tried, then the first thing you need to do is really get more help from castle group on your setup issues. I don't think your 404 is going to need rocket science to solve, and you have to get over this simple enough hurdle to get the grapes out from the garden :)

And it compiles. HasSection is an inherited method from ViewComponent. I admit if you add the CRUD row state change it would be a bit longer, but the point is if you ever tried to write on MR with the CRUD and ASP.NET, you will only found the code length difference to be even higher. All I need to do is really write another section support for is editing. Also making this example supports Ajax would also be just about another 5 lines of code to write with JS Generation.

Again I encourage you to really get MR started, also try to write your own ASP.NET control with ITemplate support (customizable table like the one I am using). At that time if you come up with additional feature, I could update my source with that and you could see the difference :)

William said...

And for #1, I am not complaining- I am saying you can't reuse what comes in with ASP.NET anyway, so in MR the simplicity to create a view component is better than providing you components thats much less flexible to use and creating one is so much a PITA.

Marcel said...

First, to be a real developer...

LOL. Yea, I know, a real programmer uses FORTRAN. (Which I did, at one time.) I'm interested in solving my problem. I can install the .NET framework and start using it. I can install MonoRail and, even with all their documentation, start hunting on Google for articles on how to fix it. You know, like a real developer.

Right.

William said...

A real developer use assembly dude :)

Ok back to the problem. I can get you start really quick by create a sample project for you, including binaries. But I still don't understand whats the difficulty. Can be more specific?
Also which version of castle you downloaded? (I encourage you to download the new successful build trunk from http://builds.castleproject.org)

The steps are simple:
1. extract the source and build binaries in a place where you store your common libaries
2. Start a new Web Application Project (NOTE: Web project, the FS style project wont work)
3. Add the following libraries (Assume you use NVelocity) from build directory:
Castle.ActiveRecord.dll
Castle.Monorail.Framework.dll
Castle.Monorail.ActiveRecordScafford.dll
Castle.Monorail.ActiveRecordSupport.dll
Castle.Components.Validator.dll
Castle.ActiveRecord.Validators.dll
Castle.DynamicProxy2.dll
Iesi.Collections.dll
Castle.MonoRail.Framework.Views.NVelocity.dll
NHibernate.dll
NUnit.Framework.dll
log4net.dll
Castle.Services.Logging.Log4netIntegration.dll
Castle.Components.Common.EmailSender.dll
Castle.Components.Binder.dll
Castle.Core.dll
Castle.MonoRail.Framework.Views.NVelocity.NVelocityViewEngine.dll
NVelocity.dll
Castle.MicroKernel.dll
Castle.Windsor.dll
Castle.DynamicProxy2.dll
Castle.DynamicProxy.dll

I have been using more and more facilities so its a bit more than you need initially. But too much is better than missing right? :)

And, stay patient, I have been there, every one start off OSS from the MS only world has been through that pain before :) Once you get used to it life will be much easier.