Fig 1 – ASP using Ajax ToolKit and LinqDataSource
It has been a while since I used one of the server side web frameworks. Microsoft .NET 3.5 ASP has some attraction, especially since the announcement of SQL Server 2008 geospatial functionality along with Silverlight, WPF, and DeepZoom. There are even hints of a Virtual Earth map element for use in Silverlight. I decided to take a learning detour into ASP Land.
With this history in mind I started into ASP land with some reservation.
Microsoft has a very extensive set of tools available for the web developer. The basic ASP controls are all there as well as Linq to SQL, and Ajax Control Kit so ASP has come a lot further than the early days of JSF. The price is a steep learning curve with a wide range of technologies to pull together. Visual Studio 2008 makes a lot of these technologies a matter of drag and drop which means that things are easier to create but more difficult to understand or tweak.
C# is a nice language for pulling all the disparate pieces together, especially since it is so close to Java in syntax and semantics. Stephen Walthers book, ASP .NET 3.5 Unleashed was an invaluable reference for my detour through ASP.
I was given an opportunity to volunteer for an energy calculator website that required a fairly complex MS SQL Server schema. This was ideal for learning ASP. I was able to adapt the SQL Server Membership capabilities to suit the website requirementsby adding some additional fields to the default aspnet_Users table of ASPNETDB.mdf. Once I had basic registration and login, complete with email notification and role based security, I could move on to the basic web application.
The first iteration involved using the new .NET 3.5 ListView control to create a basic CRUD interface (Create, Read, Update, Delete) to the required tables. ListView is a powerful Control with a great deal of flexibility. It took a little to learn all the ins and outs of events attached to the control, but I eventually had a basic approach worked out. I soon learned that Master pages simplify life considerably. Paying attention to the hierarchy of a sites page layout lets a developer abstract the unchanged into Master pages. As you go deeper into the web application a hierarchy of nested Master pages seemed to work best.
I started building my ListView controls referenced to a SqlDataSource like this:
<asp:SqlDataSource id="electricitysql" SelectCommand="SELECT Electricity.*, Locations.locationID, Locations.electricityDeliveryConfigID, energyDeliveryConfig.*, ISNULL( Electricity.monthlycharge/NULLIF(Electricity.units,0), Electricity.monthlycharge ) AS costunit, Electricity.units*energyDeliveryConfig.energyEnvironmentalImpact as co2 FROM Electricity INNER JOIN Locations ON Electricity.locationID = Locations.locationID INNER JOIN energyDeliveryConfig ON Locations.electricityDeliveryConfigID = energyDeliveryConfig.energyDeliveryConfigID WHERE Electricity.locationID=@locationID ORDER BY billdate ASC;" DeleteCommand="DELETE from Electricity WHERE electricityID=@electricityID;" InsertCommand="INSERT Electricity (userID, locationID, billdate, units, monthlycharge) VALUES (@userID, @locationID, @billdate, @units, @monthlycharge)" UpdateCommand="UPDATE Electricity SET billdate=@billdate, units=@units, monthlycharge=@monthlycharge WHERE electricityID=@electricityID" ConnectionString="<%$ ConnectionStrings:GetPluggedInDB %>" Runat="server"> <SelectParameters> <asp:QueryStringParameter Name="locationID" QueryStringField="id" Type="Int32" /> </SelectParameters> <DeleteParameters> <asp:Parameter Name="electricityID" Type="Int32"/> </DeleteParameters> <InsertParameters> <asp:Parameter Name="userID" Type="Int32"/> <asp:Parameter Name="locationID" Type="Int32"/> <asp:Parameter Name="billdate" Type="String"/> <asp:Parameter Name="units" Type="Double"/> <asp:Parameter Name="monthlycharge" Type="Decimal"/> </InsertParameters> <UpdateParameters> <asp:Parameter Name="billdate" Type="String"/> <asp:Parameter Name="units" Type="Double"/> <asp:Parameter Name="monthlycharge" Type="Decimal"/> </UpdateParameters> </asp:SqlDataSource>
The complexity of having all this sql inside a web page with additional code behind glue, didn’t appear too concise. Fortunately on further study I realized that ASP .NET 3.5 also has a new technology called LINQ to SQL. Now I could replace the SqlDataSource with a LinqDataSource and make use of all the new ORM tools in VS2008. This is basically just dragging tables from the VS2008 Server Explorer onto a dbml page and then taking advantage of all the automatically created classes that map to the SQL table rows. It meant learning a bit more of Linq query syntax, but it was well worth the effort.
<asp:LinqDataSource ID="LinqDataSource1" runat="server" ContextTypeName="ElectricityDataContext" TableName="Electricities" EnableDelete="True" EnableInsert="True" EnableUpdate="True" OnInserting="linq_inserting" Where="locationID = @locationID AND userID=@userID" OrderBy="billdate" > <WhereParameters> <asp:QueryStringParameter Name="locationID" QueryStringField="id" Type="Int32" /> <asp:ControlParameter Name="userID" ControlID="lblUser" Type="Int32"/> </WhereParameters> </asp:LinqDataSource>
Next on the agenda was delving a bit into AJAX. One requirement of this project was a set of cascading DropDownLists. The selection from the first DropDownList affects the content of the second etc. The content of each DropDownList is populated out of the database. There is a project called the ASP.NET AjaxTool Kit Included in the toolkit is this control, ajaxToolkit:CascadingDropDown, which exactly met the requirements. The ajaxToolkit:CalendarExtender is also much more useable than the simple asp:calendar.
This is quite a bit of capability for actually very little effort. So far I am quite pleased with my foray into ASP .NET land. I did, however, run into one area that caused some delay.
ListView controls are best wrapped in an asp:panel that can have a Scrollbar=”auto” attribute. This allows larger ListView table layouts to have their own scrollbars as needed by the client browser. This all works pretty well. The user can scroll around the table, sort columns, add new records etc. But, when he comes to edit a row that required a scroll, the page refreshes and the next moment the edit row is out of sight with the panel scroll reset to the top of the panel. This is quite confusing to anyone.
The best solution was to add an asp:UpdatePanel with default partial rendering. The UpdatePanel is a quick and dirty approach to AJAX. Any control inside the UpdatePanel <ContentTemplate> becomes an AJAX control with partial rendering happening behind the scenes. In the case of ListView this meant that the Edit command event reset the row layout for editing but did not lose the scroll position. This is nice.
Unfortunately there is a further twist. Scrolling is good to have but really what most would like to see is a fixed header at the top of a scrolling table. Asp controls do not have a nice property for fixed headers of tables inside a Panel control. One solution is setting style=”position:relative;” on the <tr> surrounding the table <th> elements. The header cells are then positioned relative to the top of the table regardless of the scroll position.
This fixes the column headers but the ajax refresh for editing rows will keep the edit row scrolled correctly while throwing the relative head position the scroll amount above its correct location. This is definitely not desirable. I spent a few days searching for some solution and finally came across this approach: Maintain Scroll Position after Asynchronous Postback
The BeginRequestHandler is fired prior to the partial render and EndRequestHandler after the rendering. So the workaround involves capturing a current x,y scroll position on the panel before the render is fired and then restoring these after the rendering is completed in order to put things back where they should have been left.
Too bad there is no attribute available for panels like the page attribute: MaintainScrollPositionOnPostback=”true” which only works for the enclosing Page scrollbar and will not affect any interior Panel controls.
Once this bit of workaround was applied my “fixed header Ajax partial rendering ASP ListView Scrolling Table” control with full CRUD was finally working the way I needed. The effort involved was not very substantial and now that I’m further along the learning curve I can replicate this functionality across the entire website. ASP at the very least is a good approach to rapid prototyping.
Next on the agenda is exploring the Mono project to see how much of the ASP world can be forced into a Linux OS. Ultimately it would be very useful to deploy ASP websites in an Amazon EC2 AMI instance for inexpensive scaling. It remains to be seen what problems arise in a Mono based ASP platform.