Saturday, July 28, 2007

Filtering data with the ASP.NET 2.0 GridView

Finally, something else to blog about...  Are you happy, Brad?

In implementing a full Identity Management solution, there are times when you need to supply the customer with some external utilities.  In this case, we needed a simple interface to allow the customer to manage a list of location codes, mapping them to Active Directory OUs and login script paths.  We decided that, in this case, the best solution was a simple database table and a web interface to update the information.

ASP.NET, especially with it's more recent incarnations, provides some mechanisms for building simple sites with little more than drag and drop in Visual Studio.  But as you try to build in a bit more functionality, you find that you sometimes have to tweak the built-in stuff.  (Our tweaking, here, is very simple - it's just a matter of adding the proper code to the proper events.)

The LocationCodes mini-project found me banging my head against the wall (drywall repairs: $73.26) trying to implement something as simple as a filter for narrowing down the list of items on the page.  Here's what I discovered through a combination of posting, searching and just plain messing around with it.  It's actually pretty simple...

First thing to note is that since a GridView merely displays the record set that it's given, filtering actually takes place in the DataSource control it's bound to.  Cool - the Datasource control has a "Filtering" event!  How much simpler could it be...?  Well, just to mess with you, the "Filtering" event of the DataSource is exactly where you don't place the code.  The filtering event only triggers if the DataSource's filter property already has a value.

Here's our scenario: We have a web page with a DataSource, a GridView and a couple of Buttons and a TextBox for dealing with the filter.

Here's the event firing order for the first time the page loads (We have no filter set yet.):

Object Event
Page PreInit
DataSource Init
GridView Init
Page Init
Page InitComplete
Page PreLoad
Page Load
DataSource Load
GridView Load
Page LoadComplete
Page PreRender
GridView DataBinding
GridView RowDataBound (Once for each row.)
GridView DataBound
GridView PreRender
Page PreRenderComplete
DataSource Unload
GridView Unload
Page Unload

When a filter is set, or cleared, or when an item is edited, the order of events is slightly different because we have to react to what the users intent is, meaning that we'll trigger some events programmatically.

Eliminating most of the events that don't really effect us, here are the events that occur when a user interacts with the page:

When a filter is entered in the TextBox and the user clicks the Filter button:

Object Event
Page Load
DataSource Load
GridView Load
Filter Button Click (Set Filter, Call DataSource.DataBind)
DataSource DataBinding
Page LoadComplete
DataSource Filtering
GridView DataBinding
GridView RowDataBound (Once for each row.)
GridView DataBound
DataSource Unload
GridView Unload
Page Unload

When the button is clicked, we set a Session variable to hold the filter text and force the DataSource to bind.  (For readability, the code snips presented her don't include the full method signatures.  But Visual Studio creates those for you anyway...)

Protected Sub btnFilter_Click(...) 
Session("FilterExp") = txtFilter.Text
LocationCodesDS.DataBind()
End Sub


In the DataBinding event, we check to see if there is any filter text.  If so, then set the FilterExpression property of the DataSource.  If there is no filter (the Session variable is not set) then clear the FilterExpression property:



Protected Sub LocationCodesDS_DataBinding(...)
Dim f As String = Session("FilterExp")
 
If (f Is Nothing) Then
LocationCodesDS.FilterExpression = ""
Else
LocationCodesDS.FilterExpression = _
"LocationCode Like '" & f & "%'"
End If
End Sub


When the FilterProperty of the DataSource is set, the text in the FilterExpression is basically added to the DataSource's SQL statement as a WHERE clause.  If the FilterExpression of the DataSource is set, then the Filtering event of the DataSource will be triggered.  This is where you can validate the filter expression and intercept the filtering process (e.Cancel = True).  The way I implemented this, I would check the filter expression in the Filter Button's click event.  But if you bound the DataSource's FilterExpression property to the TextBox directly, you'd need a way to stop the filtering process if, by chance, a user entered something that might be considered invalid or even malicious when attached to the underlying SQL.



Since the click event of the Filter button doesn't run every time the page loads (there are other ways to interact with page besides that one button) we need to tell the DataSource about any potential filters every time we have to rebuild the page.  The DataSource's Init event is fine place to do this.  Without this, if a user, say, clicks to edit a particular row, the DataSource will load the full record set on the postback and the GridView's row pointer will very likely end up pointing to a different row, causing the user to edit the wring data.  The code is simple:



Protected Sub LocationCodesDS_Init(...)
If Session("FilterExp") <> String.Empty Then
LocationCodesDS.DataBind()
End If
End Sub


VB .NET is kind enough to allow String.Empty to be compared to an actual empty string or to VB's null equivalent of 'Nothing'.  Thus allowing us this comparison even if the Session variable doesn't exist.  Conversion of this bit of code to C# needs to take this into account.



The last bit of functionality required for our basic filter implementation is the ability to clear the filter.  We do this in the Click event of the ClearFilter Button:



Protected Sub btnClearFilter_Click(...)
Session.Remove("FilterExpression")
LocationCodesDS.DataBind()
txtFilter.Text = ""
End Sub


That's it for basic filtering.  Next time, we'll talk a bit more about the GridView and how to work with it in regards to maintaining a proper user interface...

Wednesday, May 23, 2007

After Duress

Okay...  So I'm new to this blogging thing and a couple of days after my first post I realized that I forgot to mention something that is directly related.

But what's the protocol for blog updates?  Technically, this isn't a new subject.  How sacred are the posted blogs?  Can one alter a blog once posted without disturbing the natural order of things?  And since this is all digital, is the natural order simply 0 then 1?  I mean there's not much room for disturbing that particular natural order short of completely reversing it.  And certainly I don't want to be responsible for an alteration of that magnitude.

But I digress...  Or not, actually, since I haven't even started discussing the thought I was thinking before I was confronted with the whole blog alteration conundrum.  So now I <insert opposite of digress, here>...

In discussing the issue of certificate revocation and the delays caused by automatic verification of certain, signed .Net assemblies, I forgot to mention that it seems the verification process is intermittent.  When I'm on an airplane (as I am now, not that it matters) and I start up SQL Server Management Studio, I don't get that long delay we experienced with disconnected MIIS server.

So it seems that either the certificate is being verified against a published crl, or the system is updating it's local copy of the crl or something along those lines, and once verified, or updated, it's good for some amount of time.  So it's possible that the issue can be solved by simply opening ports through the firewall to allow the system a quick peek to the published crl to satisfy its curiosity for a while.  Once sated, the firewall can be locked down again until the system loses confidence and requires another gander at the certificate black list.

This is all speculation on my part, however.  I haven't verified this other than watching the behavior of my own system.  And digging deeper into this isn't too high on my priority list.  But if I do stumble across a definitive answer to this, I'll be sure to post it.

Or do I just update this post...?

Tuesday, May 22, 2007

Under Duress

At the last minute and at great expense, the Frobozz Magic Blog company (How many people will get that reference...?) is proud to present... My blog.

For quite some time I've been intending to start a blog, if for no other reason than to have an easily accessible repository of useful bits and pieces that I can get to from pretty much anywhere. Call it an information wallet, a portable brain or perhaps auxiliary memory, basically a place to keep all that information I can't remember just at the time I need to remember it. I'd get to it one day - if, in a moment of spare time, I could just remember to do it.

But my good friend and colleague, Brad Turner - with an evil grin on his face (I know that for a fact, as I was sitting next to him when he did this...) - has forced my hand, well both hands, actually, since I use both when typing... His recent post regarding an issue we were troubleshooting has an embedded link to what was my non-existent blog. So out of fear of Internet wide humiliation, I was forced to blog my first blog in the blogosphere.

So here's the actual blog part of this blog post...

Since most of you probably got here from the link on Brad's post, I'll not rehash the entire situation. But if, by chance, you happened upon this post via some other circuitous route, here's the basic summary:

While building a decidedly kick-ass identity management solution for a client, we ran across a situation with one of the servers in which it appeared a bit unresponsive at times and, in general, just didn't behave in a fashion similar to it's fail-over brother in a far away data center, despite being built as a virtual twin. The main symptoms were lethargic application startups and curious memory errors delivered by MIIS. After numerous troubleshooting attempts, staring stupidly at the monitor and asking questions along the lines of, "What the...?" we did finally figure out the problem.

In a nutshell, the problem was that the system was trying to verify certificates associated with certain applications, but that particular server did not have access to the Internet. So applications, in this case SQL Server Management Studio, take some time to startup because they're waiting for the timeout in trying to access the Microsoft Certificate Revocation List at http://crl.microsoft.com.

In the case of MIIS, the delay caused MA extension timeouts and seemingly unassociated out of memory errors.

There are a few ways to mitigate this issue. We chose, at least for now, to disable certificate verification through the advanced properties in Internet Explorer.

All of the details, including Event Log entries, etc. are in Brad's post, so if you haven't been there yet, go now.

Go on...

That's all I have to say for now...

No reason to hang out here anymore...