Tuesday, May 6, 2008

ADO.NET, meet DIY.NET

Sometimes it really baffles me how some things in ASP.NET turn out to be a lot harder than they really "should" be. (I think that their unofficial slogan "Do the impossible. Go home early." is totally ironic, as I've had pretty much the exact opposite experience of spending hours doing things that I thought were easy.) Today I had a perfect example of this. Our application is using MS Access as a test database, and is then switching to SQL Server for the live database. So database commands are all taken care of through the ADO.NET layer, the details of the individual databases are extracted away, and I can just flip a switch to make the changeover from one set of drivers to another, right? Wrong.

It's clear to me that Microsoft planned to allow easy changeovers between databases, due to the fact that connection strings to different databases are stored in the web.config file. Switching to a different database on the same platform is a simple matter of changing the connection string. But the problem comes when you have to switch between platforms, such as my instance of switching from Access to SQL Server. I'm sure this never comes up for a lot of projects, but I can't imagine that changing database platforms is so rare that it could be safely ignored without impeding the efforts of too much of their audience.

So the first problem of switching from Access to SQL Server is that the two databases use entirely different classes in completely separate namespaces for communication. To talk to an Access database, you have to use OleDbConnection, OleDbCommand, OleDbReader, etc. But for a SQL Server database, you have to use SqlConnection, SqlCommand, SqlReader, etc. And naturally, the two sets of classes are not interoperable. Instead, you're forced to declare your allegiance early and unequivocally. You choose one of several parallel but disjoint tracks and you have to stick with it. It's kind of Microsoft's version of "You're with us or you're against us", and I think we all know how well that one went over.

Now, I sure was not going to write code which would have required me to change OleDb objects to the corresponding Sql objects in every single one of my business model objects. Thus, I created a Database class that attempted to abstract the details away as much as possible and simplify the code that would have to go into my business models. It boils down to a method that takes in a SQL string and a data structure of parameters and spits out the result as a DataTable. Honestly though, I would've probably done this anyway, just because I like to be able to avoid writing the same code over and over again, so this in and of itself was not really that much of a problem.

However, the thing I found out today is that much of a problem. What I found is that Access and SQL Server handle parameter binding in very, very different ways. Let's say you have an INSERT query with three parameters, and you want to bind them to variables in your code. Access doesn't particularly care what you call the parameters in the query, and will simply bind your three variables to the first three things it doesn't recognize in your query. SQL Server, on the other hand, has a very strict syntax for parameters, and will expect you to associate each variable with the corresponding parameter name in the query.

This creates a serious problem when you're trying to create a database wrapper class like what I had, in particular deciding on a data structure to represent parameters for the query. Internally, ADO.NET uses an OleDbParameterCollection or SqlParameterCollection for this, but as mentioned before, I don't think it makes sense for the business models to use a database-specific variable. So we're down to collection classes that don't rely on one database or the other. I had started with an ArrayList of parameters in order, which was fine for Access. However, it did not include the parameter names, which SQL Server requires. In an attempt to associate parameters with their names, I switched the whole thing over to use Hashtables instead, which SQL Server was happy with. However, since Hashtables do not return their contents in a deterministic order, Access totally jumbled the parameters, resulting in complete failure of my queries. I had the same result with SortedLists too. So nothing I tried would work for both databases.

So in the end, given the time pressure I was under, I essentially raised the white flag and created two copies of my business models - one of which sends an ArrayList to an OleDbConnection for Access, and one of which sends a Hashtable to a SqlConnection for SQL Server. If I had more time, I might've looked into using an ArrayList of Pair objects, the first item being the key and the second item being the value, but unfortunately I didn't have that luxury. Instead, I'm going to have to live with a really ugly and easily breakable solution. I really just don't understand how Microsoft could spend this much time on a database layer if I was going to wind up writing so much on top of it anyway.

The punch line to this story is that I have had experience with database layers that let you change to a database on a different platform with the flick of a switch (of course, after you clean up incompatible SQL that needs to get fixed no matter what database layer you use). In particular, I've used PHP's ADOdb class library to switch from Access to MySQL completely painlessly. What's funny about this is that the ADOdb project was developed entirely independently from both Access and MySQL, and yet it is able to bridge the gap perfectly - as opposed to this instance of three technologies (ADO.NET, Access and SQL Server) all created by the exact same company that cannot work together at all. It really boggles the mind.

EDIT: I wound up resolving this by using the ArrayList of Pair objects to pass parameters. Additionally, I created an abstract Database class that both my SQL Server class and my Access class inherit from. This class has a static method that will return either the SQL Server object or the Access object based on the value of a static Boolean flag. So now, I can switch from Access to SQL Server just by flipping a switch and correcting the incompatible SQL statements, but it's still pretty clear that ADO.NET did not work this way right out of the box.

Event Order

Well, it's time to start off with that old ASP.NET standby, the order of events in the webpage. Now, we've all seen the pretty diagrams explaining every event in excruciatingly verbose detail, so I'm going to try to boil it down to the essentials. What do you absolutely need to know about the order of events, and more importantly, what difference does it make to you?

If I had to give the absolute minimum description, I'd say that there are three principal stages of an ASP.NET page - Init, Load, and Render. Init is where the controls are created and placed on the page. Load is where the controls' default values are populated from Viewstate. Render is where the HTML of the pages is generated. Beyond those three stages, there's another important detail that I feel gets lost in the shuffle sometimes - control-level events such as button clicks are handled between Load and Render.

So, what all of does this mean for you? There are a couple of takeaways. First of all, since the Init phase creates the controls on the website, don't expect to be able to reference them in PreInit, since they're not there yet. This limits the usefulness of PreInit, but it does make PreInit a good place to put logic that relies strictly on server session or other items not found in controls. For instance, I placed my login check in PreInit. The login check uses server session to determine if the user's logged in or not, and if they are not, they get sent to a login screen. I felt that putting the login screen in PreInit saves a bit of time, since I don't have to load all the controls unnecessarily.

More importantly, since control-level events come between Load and Render, if you're setting the values of controls yourself, you need to do so in PreRender. If you do it before that, then your control-level event will get the values that you just set, NOT the values that the user had actually posted. This means that things like the Text property of text boxes, the SelectedValue property of dropdowns, etc. will not work. You can work around that by using Request.Form on everything, but there's no reason to do that if you don't have to. Conversely, if you're dynamically creating your controls from scratch, that needs to be in Load. If you do it in PreRender, then the event handlers won't be set up by the time it tries to process control-level events, so your handlers will never get called. The result is that you click a button and nothing happens. So yes, it's annoying to dynamically generate a page in a two-step process like this, but that's the way you have to do it. If it's really a pain to split up the process into two steps, I'd recommend doing everything in Load and using Request.Form in your event handlers.

So hopefully that helps clear up event order and what it means to you.

Thursday, May 1, 2008

Welcome!

Hi everyone! This is my new blog about web development and ASP.NET.

So, here's a bit of background about me - I'm a first-year graduate student studying Human-Computer Interaction. I am currently finishing up a project which involves implementing a web interface in ASP.NET 2.0. This has been my first real project in ASP.NET (outside of a school project which doesn't really count), as all my previous projects (including 3 years of work at a PHP web development firm) have been in PHP and Perl. I found ASP.NET difficult to learn, and I often had to resort to random Google searches to figure out why it wasn't working the way I expected it to. It often took a lot of reading of conceptual stuff and some code examples before I figured out what was really going on. So basically, with this blog I'm looking to create the type of resource that I really would've liked to have while I was working on this, in the hopes that some budding ASP.NET programmer out there on the Internet can find it and get answers to their problems.

Before I get started with ASP.NET, I thought I'd offer some initial thoughts with it. In my mind, the principal problem with ASP.NET is that it is fundamentally a mixed metaphor. The .NET framework is clearly intended to make web development "just like" working on a desktop application, but it doesn't work completely. The most notorious instance of this is that sometimes, depending on what you do with your controls, it will lose the data in your fields. This is because web applications, by their very nature, are built on HTTP which is stateless and therefore doesn't really remember what you did in the previous page. (As a side note, I think Adobe Flex, from what I've seen of it so far, does a much better job in supplying this kind of platform for web applications, but then of course Flex requires Flash in order to run.)

Secondly, ASP.NET is simply too big and too complex to learn it all by yourself. There are too many classes, too many properties, too many models, etc. In fact, I'd be surprised if anyone (including Microsoft employees!) has a complete understanding of everything in ASP.NET. The sheer complexity makes it very difficult for people who learn best by poking around (such as myself, and I'd imagine a lot of other programmers) to pick it up. (But hey, it means that Microsoft gets to sell a lot of books and courses and tutorials and make a lot of money!) Finally, ASP.NET is hardly the fastest language / framework out there, as I will get into later.

I'm not going to totally bash ASP.NET. I think the code-behind model is a fantastic idea and is pretty well implemented. In particular, being able to create user controls, each with its own self-contained codebase and properties, was a great help in my development process, and something that I have not seen in any other language I know about. I've also been spoiled by Visual Studio, which is amazingly smart for an IDE (and given the nature of programming and IDEs in general, that's saying quite a bit). I think it's even better than Eclipse.

So that's my intro pretty much. Feel free to comment!