Thursday, 20 September 2007

ItemCreated Strikes Back

Being an old member of the DragNDrop fan club, I'm happily using things like ObjectDataSource in my Asp.Net pages. So, just yesterday I was fighting with the same problem for the, like, 10th time, so I decided to blog about it and maybe next time I'll be wiser.

The ObjectDataSource works great after you've tuned it for your needs. It was a long story about how I have married it with Neo (my ORM tool of choice), but now that all the plumbing code is autogenerated, I can create a simple administrative page in several seconds, while still keeping my UI, model, and data access separate. (Yes I know that you can do it even faster with Rails) The problem arises when you try to do some data processing that doesn't fit well into your model, since it is more view-related.

Let me explain. I'm trying to make a page that would allow to edit the schedule for a fitness club. The page has, naturally, a table: columns are days of the week, rows are hours, each cell has the trainer name and the course title. I've got two ObjectDataSources -- one for the hours and one for the days of the week. And I have a repeater inside a repeater.

Now, you see that there is no way of doing this via just declarative databinding. Even if there are no records about a particular hour/day combination, there should be a table cell. So, I tried to grab the data using the Repeater's ItemDataBound event. The Item here is of type RepeaterItem, and it has a DataItem property that gives me the day of the week. Next, we have to travel up the control hierarchy and retrieve the parent repeater's item, to get the corresponding Hour object. Now we are ready to retrieve the data for the corresponding cell. Next, we have to manually find the corresponding controls inside the cell and fill the data.

Doesn't sound like it's the year of 2007, eh? It actually reminds me of funny tricks I used to do with classic ASP.

The real fun, however, begins when you try to update the values. The truth is, you can't use the ItemDataBound event since it isn't fired again after you clicked the Update button. This makes sense, since the data has been fetched already, so there's no need to fetch it again. The ItemCreated event is fired, however. But the DataItem property is null (as it was the first time before the postback). So, thinking again, you don't have access to your data items at all: they are not persisted, what you have is just property values saved in the ViewState. Thus, no chance to retrieve the data in order to update the correct record with the new values.

There are several workarounds here: you can refetch the data and keep it in a private variable, you can persist HourID in the ViewState and retrieve it on postback, you can even use hidden fields, but all of these are equally ugly. The point is, the DragNDrop thing just doesn't belong here, let's not force it to work. Back in the Asp.Net 1.1, I'd pick a data source manually and have complete control over it. This is exactly what I'm going to do now.

So, the (trivial) lesson is: mainstream solutions work only in mainstream situations. If you have a straight table, go the ObjectDataSource way. If the table is complicated and the data doesn't quite fit, go the manual data source way. If the table is weird -- displays running totals every 10th row, for example, it might be even OK to remember the foreach loop with tr's and td's. Let's just hope it won't get too weird.

Sadly, if I were using TDD for ASP.Net, I won't get into that trap.

No comments: