A collection of learnings and opinions.

Wednesday, June 27, 2007

Error ... error ... whence cometh thou?

I'm working on a page that will allow our users to view a table in a database in ASP.Net. To do this I am writing a UserControl where I am connecting to the database through a SqlDataSource, and I bind a GridView on the UserControl to this datasource. If you are unfamiliar with databinding this simply means that I hook the presenting controller (the GridView) up to an object that can provide it with data. This automagically populates the presenter with the data from the source. But, as with all automagical things can, and do, go horribly wrong.

Small digression: I'm from the Java plains, but I've recently set up shop in the .Net woods where I mostly hang around in the soppy marshes of ASP, the glossy banks of MSSQL-RS and more recently the crags of ADO. Being quite new in these parts I get to do a lot of really stupid things, and I'm building an appreciation of each land's pros and cons. One of my gripes with the marshes of ASP is the incessant focus on doing things declaratively as the first-choice. Sure, it's great if what you want is static, but I don't want static content!

Why this rant? Declarativity just bit me in the rump, like a particularly nasty ass.

So, I was binding my GridView to the SQLDataSource declaratively, like a good ASP-ape. Everything was working as expected until I started testing the page with non-standard inputs. The datasource is setup with the data it needs to connect to the database, and an initial select statement to get at the data you want. If any of this is incorrect the entire page explodes in a messy way. Red goo all over the walls.

My test-case was simply setting up the datasource with an incorrect login. This caused an SQL-exception when the SQLDataSource tried to perform the initial Select (as one would expect). Now, .Net does not have checked exceptions (expect a post on this later), so being from the Java-land I am always a bit surprised and worried to see exceptions being thrown willy-nilly run-time.

Well, this exception I should be able to deal with. It is a real problem, but the application should be able to handle such problems gracefully. I just had to find some place to catch this exception, and handle it.

There are several places you can handle an error in ASP.Net:
  • global.asax (where you can handle application-level errors), overriding one of the following methods
    • Gobal_Error
    • Application_Error
  • Page_Error (where you can handle errors on a page-level)
In addition to this you can define a page to send the user to in case of an error, so the plebs don't have to see the ugly innards of your app. All of these solutions are good, but not for my use-case where I actually wanted to handle the exception, should it occur. These solutions let you do things like log and redirect on errors, but offer little in the way of actually handling the problem.

Yes, on a page I could have used the Page_Error, the problem is that I am building a UserControl, which is part of a page, but not the page itself. And, a UserControl has no onError event to catch, and on Error method to override. In effect the UserControl blew up, killing all bystanding controls and page-elements.

Digging in to where the error originated I saw that it was spawned when the controls on the page where coming to life, in the preRender -stage of the lifecycle. This tells me that I must place some code to handle this before that stage of the lifecycle to avoid this error. In the purely declarative mannar I would declare a handler for some SqlDataSource error event, but that component does not have one. So I am relegated to provoking the error myself, and handling it correctly.

This is not really all that hard, once you've gotten to this level (the realisation that I had to do it this way took much longer than the coding of the fix). What you're forced to do is to not bind the GridView to the SQLDataSource declaratively. Read that again. To handle the error you're forced to step out of the declarative world and do the binding yourself. Now, this isn't hard, but it suddenly seems as if the declarative way of doing things is second-rate and sunshine-days-only. I can live with that, but what I really don't like is how every example focuses on declarative implementation, when it's the less powerful and (to me) less natural way of doing things. Sigh.

Oh, well, on to the fix. Just remove the DataSourceID="someDataSource" element from your GridView decalaration, and add something like this to you Page.Load handler:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
theGridView.DataSource = someDataSource
btnSave.Visible = True
lblError.Visible = False
Catch exc As SqlException
lblError.Visible = True
btnSave.Visible = False
lblError.Text = "Could not load table: " & exc.Message
End Try
End Sub

This will let the UserControl come up, even if the datasource experienced an error. Much better, but I'm left feeling it's dumb that I have to do it this way.

Technorati Tags: , , , , , ,

Tuesday, June 26, 2007

Let's Talk About It...

"Let's talk about it..." that's the name of the latest version of Jade, that was released yesterday! That's right, a new version of everyone's favourite FIPA-compliant Java-based multi-agent framework has a new release. And this is just, what, a month or two since the first book about the framework was released (Developing Multi-Agent Systems with JADE).

Exciting times!

So, what's new in this release? Any breaking changes? Let's have a look at the press-release (I haven't had time to try it out yet. I'll do that at my first opportunity and post my impressions).

  • A whole new communication mechanism: Publish/Subscribe!
    • A whole new mechanism where agents can simply register a topic they are interested in, and every message that is broadcast with that topic will be sent to them. This sounds like a good idea to me, uncoupling the broadcaster and subscribers. It is only applicable to general broadcasts, though, and I wonder how they implement "closed" lists.

    • Sounds like they are implemented by making a proxy-AID representing the topic, to ensure compatibility. I wonder if this is a good thing, is breaking the agent-AID binding really something you want to do?

  • A "completely restructured version of the Web Service Integration Gateway"
    • Web app (can be deployed to app servers, like Apache Tomcat)!
    • Web-services are derived from the actions in the ontologies registered to the Directory Facilitator.
    • WSDL document generated for each agent service exposed (how did they do it before?)
    • I haven't used the WSID earlier, so I can't really add any thoughts to this, check out the guide here.

  • Improved detection of main container using UDP Multicast packets

    • Excellent, this makes it easier to set up multi-platform environments!
  • A reflector, finally allowing us to use the Java Collections Framework when working with ontologies
    • Excellent stuff, now just port the entire thing to Java 1.5 to give us generics!

All in all some exciting stuff! Let's hope this one doesn't break anything, eh? I've not touched it yet, and so far on the mailing-lists there's one user who's had problems with the multicasting on his machine, but it looks like a he found a workaround (detect-main option set to false).

Let the bugsearch ensue :-)

If you met God on your commute

Here's a nice little story...

Talking to God...

Monday, June 25, 2007

Which tables do I have available?

Sometimes when you're connecting to a database you'd like to know what tables are available. I'm no DBA, so it took me a while to find out how to do that.

In this case I've just gotten the connection stuff from the user, and I run this code when the user presses a "Connect" button to connect to the database the user has specified and populate a drop-down box with the user-tables in that database. The real meat here is the selectTables string in there, which gets the user-tables from the sysobjects table. Once I get this I bind the drop-down box (ddTableSelect) to these to show all the tables.

Protected Sub btnConnect_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnConnect.Click

Dim builder As New System.Data.SqlClient.SqlConnectionStringBuilder

builder.DataSource = txtDatasource.Text

builder.InitialCatalog = txtInitialCatalog.Text

builder.UserID = txtUserID.Text

builder.Password = txtPassword.Text

Dim selectTables As String = "SELECT sysobjects.name, sysobjects.id " _

& "FROM sysobjects " _

& "WHERE sysobjects.type = 'U'"

Dim source As SqlDataSource = New SqlDataSource(builder.ConnectionString, selectTables)

ddTableSelect.DataSource = source

ddTableSelect.DataTextField = "name"




Catch ex As Exception

lblError.Text = "Could not connect: " & ex.Message


End Try

End Sub

To be perfectly clear, as VB can be a real pain on the eyes (and SQL is soooo much better, right!?) this is the SQL that gets the tables:

SELECT sysobjects.name, sysobjects.id 

FROM sysobjects

WHERE sysobjects.type = 'U'

Yes, I know, catching all exceptions in the end there is bad, but this is me feeling my way, ok?

There's a bug in the compiler

Or so they say.

I was making a new page-control today for my app. Using VS this can be done by adding a new controller to the page through menus, or you can just do what I do. I copy an old one, and paste it in, changing the Class names and removing most of the code. This bypasses all the tedious setup, and all the bits that have to be there for the framework I'm working on are already there. Goodness.

Happily I build, expecting no errors, but I get a nasty message. "Custom_Report is ambiguous in the namespace 'ASP'". Not good. Custom_Report was the name of the control I copied, so I go back and double-check that I changed the class-name. Yes, yes I did.

That's strange. Well, a quick search online told me that this might be a result of a known bug in the compiler. The fix was easy, you have to compile your app in non-batch -mode. How do you do this, I hear you asking. Simple: enter the compilation section of your web.config, and set batch="false", as so:

<compilation debug="true" batch="false">



This has been your small-problem, small-fix.