Server Side Detour


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.

JSP is similar to ASP but never seemed compelling to an individual developer without the division of skill sets found in larger teams. By coding my own servlets instead of deferring to a JSP engine, I found I had more control that translated well into the ping-pong dynamic rendering now called AJAX. My prior experience with server generated html using JSF was not especially fruitful either. Admittedly it was a number of years ago when JSF was quite new, but at that time it was, you might charitably say, “clunky.” The round trips to the server for every little user interaction were not conducive to a good user experience. I guess I didnt find the complexity of a server side JSF layer too compelling, since I could manipulate SVG myself with javascript and send Msxml.XMLHTTP for any parts of my SVG that needed dynamic re-rendering. Now that higher bandwidth is more common, round trips are a bit more responsive but refresh blinks are still noticeable.

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.

Oversimplified, ASP, like JSP, adds special namespace controls that can be interspersed into normal html. The resulting web form files named with an .aspx extension are dynamically compiled on the server. This server side code in turn is used to create html sent back to the client browser. In addition to Javascript, ASP lets you have a lot of server side control with partial class code-behind created for each aspx page. One nice benefit is that much of the idiosyncrasies between browsers is handled by the compiler instead of the developer.

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

    <asp:ScriptManager ID="electrcityscriptmanager"
              EnablePartialRendering="true" runat="server"/>
    <script type="text/javascript">
	    var xPos,yPos;
	    Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler);
	    Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);
        function BeginRequestHandler(sender, args)
        {
            if($get('<%= DataTableView.ClientID %>') != null){
                xPos = $get('<%= DataTableView.ClientID %>').scrollLeft;
                yPos = $get('<%= DataTableView.ClientID %>').scrollTop;
            }
            else {
                xPos=0;
                yPos = 0;
            }
            //alert(xPos+","+yPos);
        }

        function EndRequestHandler(sender, args)
        {
            if($get('<%= DataTableView.ClientID %>') != null){
                $get('<%= DataTableView.ClientID %>').scrollLeft = xPos;
                $get('<%= DataTableView.ClientID %>').scrollTop  = yPos;
            }
        }
</script>

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.

Posted in ASP