ASP.NET (RSS)

A Better Pager for the ASP.NET 2.0 GridView

The default pager on the ASP.NET 2.0 GridView is not pretty, and is barely practical. I've tried styling it and mucking around with the RowDataBound and RowCreated events behind the scenes, and finally I thought I'd just have to learn to live with its limitations.

This week I discovered some great pager code on CodeProject by Daniel Vaughan, and below I've shown the difference that the "Amazon-esque Pager" control made for me:

Daniel even took the time to answer my questions on getting his C# code working with databound gridviews. Brilliant!

If you're frustrated by the limits of the in-built Pager, give Daniel's code a try and improve the situation for your users at the same time.

Tags: pager, asp.net, gridview, amazon, codeproject

5 Tips for Working With BugTracker.NET

Recently I downloaded BugTracker.NET, an excellent ASP.NET/C#/Sql Server open-source bug tracker developed by Corey Trager. Set up was very simple - just read the README file!

I really like how flexible BugTracker.NET is. Below are 5 of the simple changes I made, to make the product better suit my needs.

1. Set a different background color for "Closed" Bugs
"Closed" bugs are marked with status 5 (by default). You can enhance the built in background colors on "bugs.aspx" by checking for status 5 and setting a different background color in queries.

First go to the "queries" page, and select edit on one of the queries. Then, incorporate the snippet below which sets the first returned field to a light grey background color if the bug is closed:

select CASE WHEN ISNULL(bg_status, 0) = 5 THEN '#cecece' ELSE isnull(pr_background_color,'#ffffff') END, ...[rest of query]

Note that this first field has no alias, and needs to start with a hash character to be used in the "bugs.aspx" screen as a color.

2. Play around with CSS for your own styles
The main cascading style sheet for BugTracker.NET is called "btnet_base.css". Any changes you make to this file may be overwritten during an upgrade, so make sure you save a copy.

The first change I made was removing the hard-coded text sizes (for example, "8pt") and replacing them with relative EM values. Here's a link to my current "btnet_base.css" file with notes on the CSS selectors I've managed to figure out, ready to make your own customisations: BugTracker.NET 'btnet_base.css' Customisations

3. Use the "User-Defined Attribute" in queries
At first I removed the user-defined attribute using the web.config file, but I quickly brought it back in and used it for "sub projects". Here's an SQL snippet to get the user-defined attribute to appear in queries (first, go to the "queries" screen and edit a query):

SELECT [rest of query]...isnull(uda.udf_name, '') AS [user defined attribute]...[rest of query]...
FROM ...[normal from clause, which will include the "bugs" table] left outer join dbo.user_defined_attribute uda on bugs.[bg_user_defined_attribute] = uda.[udf_id]

It's important to use a LEFT OUTER JOIN to the "user_defined_attribute" table so that bugs without any user-defined attribute will still be returned.

4. Create useful categories
I used Bugzilla's "Severity" field as a model for creating useful categories. These new categories work hand-in-hand with priorities. My text is fairly verbose - you might want to go with something simpler like Bugzilla's severity definitions which I found here.

Here's my new category list (you can see I also use it for tracking enhancement requests, as opposed to just bugs):

  • Application Unusable
  • Critical - no workaround
  • Major - workaround exists
  • Normal
  • Enhancement Request
  • Minor Cosmetic Request

5. Set the "AbsoluteUrlPrefix" in web.config
There's lots of well-documented changes you can make to web.config. If you're using e-mail notifications, you need to remember to change the "AbsoluteUrlPrefix" key to point to your virtual directory.

For instance, if you've installed BugTracker.NET on a server called DOGBERT in the virtual directory BTNET, your new "AbsoluteUrlPrefix" key would look like:

<add key="AbsoluteUrlPrefix" value="http://dogbert/btnet/" /> 

I hope these tips help in your use of BugTracker.NET. Of course there's a lot of room for customisation of the source code or database tables, however, this is outside the scope of this article.

Good luck!

Tags: bugtracker.net, bug, customisation, development

Autocompletion ASP.NET Control

There are many auto-complete/auto-suggest web controls around, and when I needed AJAX functionality to get the data from the server dynamically, in preferably an ASP.NET control, I started using the excellent "Implementing an Ajax.NET-based Lookup Server Control" on CodeProject.

This is a nice control that works in with AJAX.NET and has many configurable properties. Unlike some of the other similar controls I've found, it also supports keyboard selection of the returned matches (using the up and down arrow keys). And, since it uses AJAX.NET, it can take advantage of Session variables (should you need them). All this is packaged into a replacement control for the standard textbox.

Unfortunately it's beyond me to get more than one of these controls working on the same page - doing this would require some serious javascript kung-fu which I do not possess. So, I did some research and eventually settled on SimoneB's ASP.NET wrapper of the script.aculo.us autocompletion control.

SimoneB has done a fine job - using her sample video, I was up and running in a couple of minutes, and I could use multiple controls on the same page. Also, script.aculo.us brings some nice javascript-based animation into the bargain and SimoneB has made it easy to add a "loading" GIF too. Keyboard support is still there, but unfortunately I can't seem to find how to use ASP.NET Session vvariables (no matter, this can be worked around). Although I believe the script.aculo.us control does cache data, it's not quite as configurable as with AJAX.NET.

One other criteria was that the control by quick to implement. I had a limited window which prevented deep research, in-house development or even the possible purchase of a more fully-functioned control.

Some other alternatives that I have not personally used, but look promising, include GoogleSuggestCloneJax, AJAX.NET sample, AutoSuggestBox, capxous ($), CPAN Suggest, EBA: Web ComboBox V3 ($) and BComplete.

Out of all of these controls, BComplete looks interesting because it overcomes the problems of a lot of items being returned by providing a top and bottom scroll section (most of the other controls take the approach that they'll only return up to 10 or so items, so the rest of the matches aren't unviewable off the page). I lessened the impact for my users by matching after they'd typed at least two characters, which makes business sense in my situation.

Overall the new control is far better in my situation than the standard ASP.NET drop-down, to allow users to type and have auto-suggest, as opposed to only being able to select from a list. Also, page size is reduced because all the options don't have to be pre-loaded.

Tags: asp.net, auto-complete, auto-suggest, javascript, ajax

Update June 14th 2006: Ryan Homer has extended and improved the autocomplete control on CodeProject (not looked at, but might be helpful) to allow multiple instances.

Infrequent Validation of viewstate MAC failed Exceptions in ASP.NET 2.0 Site

I've been developing an ASP.NET 2.0 site for a couple of months now (which got released and used by 100+ people, with no major bugs...high five!)

Every so often, I see a strange error message coming through the logs that reads "Validation of viewstate MAC failed". I'm using a GridView control, bound to an ObjectDataSource, with EnableViewState turned off and DataKeyNames. After dropping into "web developer" mode - where I search the web for the answers to my development problems :-) - I found two helpful sites, at Jotekes Blog and the ASP.NET Forums, which addressed the problem.

It seems that in my case, the problem was occuring with pages that took a while to load, where users were clicking on an edit button in the GridView before the page had fully loaded. The error was raised because a hidden form INPUT necessary for ASP.NET's internal workings called "__EVENTVALIDATION" had not yet been rendered (and thus wasn't passed to the edit link).

I implemented the suggestion on Jotekes Blog and disabled event validation, and view state encryption:

<pages enableEventValidation="false" viewStateEncryptionMode ="Never" />

Making this change saved me some bytes in view state and totally removed the hidden "__EVENTVALIDATION" section at the bottom of the page. My understanding is that from a security standpoint, after making these changes, someone could attempt to POST to my page and I wouldn't be able to tell if the POST came from the same page (this is the event validation part). Also, someone could read the view state.

Given my environment - an internal network (intranet) using Windows authentication - I feel that the security implications of disabling encryption on the viewstate are pretty small. I used Fritz Onion's ViewState Decoder to check out what was contained in the viewstate for the GridView, and it was pretty much the ID's for the records, which were needed for edit and delete functionality. I don't believe that knowing a record identifier would be of much help to an insider on our network.

As for the event validation, I can't see much of a problem (although I can see that turning this off in a public-facing website would be a bad idea).

The advantage is that users can click on edit links in the GridView and start editing. The advantage is that users don't see exceptions, when trying to do their jobs.

Hopefully I'm not missing something!

Tags: viewstate, asp.net

web.config, Windows Authentication, and getting the logged-in user's identity

I've been developing an ASP.NET 2.0 web site on my machine (and I love the in-built web server) which accesses an SQL Server 2000 database through an SQL account, but which also passes the current user's Windows login for row-level access. This works fine in development when the web.config file is set up like:

<authentication mode="Windows"/>

In this situation in development, my Windows login is returned when using code like System.Security.Principal.WindowsIdentity.GetCurrent(), which is what I want.

But, when I tested the deployment of the site on Windows Server 2003, the current user always returned NT AUTHORITY! So, after checking all the possible settings in IIS (and comparing settings to sites I *know* get the current user), I discovered the following on a page of PAG documentation on MSDN:

Impersonation Options

You can use Windows authentication with ASP.NET in a number of ways:

  • Windows authentication without impersonation. This is the default setting. ASP.NET performs operations and accesses resources by using your application's process identity, which by default is the Network Service account on Windows Server 2003.
  • Windows authentication with impersonation. With this approach, you impersonate the authenticated user and use that identity to perform operations and access resources.
  • Windows authentication with fixed-identity impersonation. With this approach, you impersonate a fixed Windows account to access resources using a specific identity. On Windows Server 2003, you should avoid this impersonation approach; instead, use a custom application pool with a custom service identity.

The second option was exactly what I wanted, and can be accomplished by simply adding the following line to web.config (I added it after the "authentication" section):

<identity impersonate="true" />

Problem solved! I hope this might help anyone else in the future...and I know I'm probably going to need to refer back too.

Regular Expression to Prevent Users Entering Malicious (HTML) Form Data

Cross-site scripting (XSS) is a problem that ASP.NET helps you deal with by not allowing any "malicious" (I'm interpreting this as HTML tags, whether it's <0BJECT> or <i>) input in the Request object, by default. This behaviour can be switched off by setting the "ValidateRequest" Page directive to "false" and you can do your own validation à la Peter van Ooijen's "Protecting an ASP.NET page against malicious input with ValidateRequest (A potentially dangerous Request.Form value was detected)" post.

In my case I left the default setting on - I'm not good enough to catch all possible vectors of attack - but used a RegularExpressionValidator validation control, with a regular expression of "^[^<>]+$" (without the quotes) to detect if there are any angle brackets in the field. For later-version browsers, the validation controls are rendered as client-side Javascript which could possibly be bypassed, but that doesn't worry me because ASP.NET also handles the problem server-side and if malicious text did make it to the server, a System.Web.HttpRequestValidationException exception is raised which I handle through my error page.

The validation control merely forewarns the user and in my mind is enough to prevent accidental or curious users from entering HTML tags in free-text data entry fields.

Note: I figured out the regex using http://www.regular-expressions.info/reference.html - the regex matches strings from start to end (the first ^ character and closing dollar sign) where there are no occurences of the characters in the square brackets. Initially I thought that the angle bracket characters would need to be escaped, but they don't.

Adding a sort direction image to a GridView's header - ASP.NET 2.0

The ASP.NET 2.0 GridView control has a simple property called "AllowSorting" which makes it easy for end-users to click a column header (rendered as a link when the "AllowSorting" property is set to "True") and sort to their heart's content - a second click on a sorted column even sorts in descending order. However, I reckon the built-in functionality is missing an image or glyph or something to show that the column has been sorted.

I notice there's a few articles on adding a sort direction image to a header cell in an ASP.NET 2.0 GridView, but I wanted to write about a simple method I came up with that uses the Webdings font (and no images...OK, so the title of this post is a little misleading). This technique works well for me now because a) its impact on the page's code is minimal and b) it uses a font for now, but eventually it could use an image.

In the GridView's "Sorting" event, add the code:

        AppendSortOrderImageToGridHeader(e.SortDirection, e.SortExpression, [gridname])

And here's the code for "AppendSortOrderImageToGridHeader", which I have put in a class in the "App_Code" folder:

        ''' <summary>
        ''' Append a sort indicator (in this case, a symbol in Webdings font) based on
        ''' the sort expression and direction that are passed to the correct column header
        ''' in the passed grid. 
        ''' </summary>
        ''' <param name="sortDir">Sort direction - either ascending (<c>SortDirection.Ascending</c>) or descending (<c>SortDirection.Descending</c>).</param>
        ''' <param name="sortExpr">Sort expression which is a database field.</param>
        ''' <param name="grid">ASP.NET GridView control.</param>
        ''' <remarks><para>Called from a grid's "Sorting" event, e.g. <code>AppendSortOrderImageToGridHeader(e.SortDirection, e.SortExpression, [gridname])</code>.</para></remarks>
        Public Shared Sub AppendSortOrderImageToGridHeader(ByVal sortDir As System.Web.UI.WebControls.SortDirection, _
                ByVal sortExpr As String, _
                ByRef grid As System.Web.UI.WebControls.GridView)

            ' looping variable 
            Dim i As Integer
            ' did we find the column header that's being sorted?
            Dim foundColumnIndex As Integer = -1

            ' constants for sort orders
            Const SORT_ASC As String = "<span style='font-family: Webdings; '> 5</span>"
            Const SORT_DESC As String = "<span style='font-family: Webdings; '> 6</span>"

            ' get which column we're sorting on
            For i = 0 To grid.Columns.Count - 1
                ' remove the current sort
                grid.Columns(i).HeaderText = grid.Columns(i).HeaderText.Replace(SORT_ASC, String.Empty)
                grid.Columns(i).HeaderText = grid.Columns(i).HeaderText.Replace(SORT_DESC, String.Empty)
                ' if the sort expression of this column matches the passed sort expression, 
                ' keep the column number and mark that we've found a match for further processing
                If sortExpr = grid.Columns(i).SortExpression Then
                    ' store the column number, but we need to keep going through the loop
                    ' to remove all the previous sorts
                    foundColumnIndex = i
                End If
            Next

            ' if we found the sort column, append the sort direction 
            If foundColumnIndex > -1 Then
                ' append either ascending or descending string
                If sortdir = SortDirection.Ascending Then
                    grid.Columns(foundColumnIndex).HeaderText &= SORT_ASC
                Else
                    grid.Columns(foundColumnIndex).HeaderText &= SORT_DESC
                End If

            End If

        End Sub

The code in "AppendSortOrderImageToGridHeader" loops through all the columns headers, once, to remove any previous sort "image" and to also find the column that is being sorted. Then it adds either an ascending or descending Webdings character to the column that's being sorted.

I'm only two months into the project, and still learning ASP.NET, but perhaps as time goes on I may switch to the ASP Alliance article which extends the GridView to add a SortDescImageUrl and SortAscImageUrl property.

As usual I make no guarantee that this code is going to work outside my alternate reality...but, I hope you find it useful!