Monday, January 7, 2008

Media Center

Components
  • Antec New Solution Series NSK2400 Mini Tower Case (Silver) Retail (380W power supply)
  • Intel Core 2 Duo E6300 Conroe Processor 1.86GHz, 1066FSB, LGA775, 2MB Cache Retail
  • Corsair TWIN2X2048-5400C4 2GB DDR2-675 XMS2-5400 Xtreme Performance Memory w/Black Heat Spreader Retail
  • MSI 945GCM5-F Intel Core 2 Duo (Desktop) Socket 775 1066 MHz PC2-5400 (DDR2-667) mATX Motherboard Retail
  • EVGA e-GeForce 7600 GS (256-P2-N549-TX)
  • Hauppauge WinTV-PVR-500 MCE
  • Western Digital Caviar SE16 (750GB OEM)
  • SAMSUNG SH-S203N 20X SATA DVD Burner Black Drive Bulk
  • Vista Ultimate
Notes
  • Assembled with no problem. Well, I was frightened during the attachment of the Intel heatsink / fan. I thought that I was going to snap the mobo into two pieces. Started up with zero problems at all, which I assume is a credit to modern mobo architecture.
  • Divx and Xvid codecs seem to get installed somewhere between BeyondTV 4.4 and 4.7

Problems
  • I initially installed Vista Home Premium. This was a big mistake because it doesn't have Remote Desktop.
  • Live TV is not at the quality that I would prefer. Channels 2-5 come in great, but the other channels seem to experience signal interference. I'm sure that I'll be able to resolve this problem in the future.
  • Haven't experienced any heat-related problems yet, but it is a concern for the summer months. The media center sits in my entertainment center behind a glass door. The only ventilation is through a 1.5" by 6" slit in the back through which wires are run.
Conclusion
I'm really happy that I built this and wish that I had done months ago.

Friday, December 7, 2007

Just Say No To Software Development Reference Books!

Someone asked today if I could recommend any TFS books. "I don't have any books- I have Google," I replied. This was sort of a flippant answer but it's the truth. I didn't know how prescient this statement was until I ran into a problem later in the day.

I've recently been working on bidirectional communication between Sharepoint 3.0 and TFS 2005. Toward the end of the project I ran into a problem relating a TFS display name (such as 'John Doe') to a Sharepoint account name (think 'DOMAIN\doejoh'). After a little research I developed a theory that the IGroupSecurityService interface was going to play an integral part in my quest for account names. I plugged 'TFS IGroupSecurityService' into Google and out popped the exact answer that I was looking for. I'm not a Google cheerleader and I love books, but how could I have found this information in some overpriced, overlong book from WROX, Apress, O'Reilly, etc.?

I read a lot of books about history. I think that it's fascinating that we as a species have quite suddenly developed this ability to have all of this information available in an easily searchable format.

Thursday, December 6, 2007

Most Exciting Visual Studio 2008 Feature


There's a lot of talk about LINQ, WPF, WCF, etc., but my favorite new Visual Studio 2008 / .NET 3.5 feature so far is the 'Remove Unused Usings' function.

Tuesday, November 20, 2007

Manipulating SQL Server Reporting Services files with PowerShell

My company is addicted to reports.

We recently discovered that there was a bug involving Print Layout mode in the Report Viewer component and SQL Server 2005 Service Pack 2. I was suddenly confronted with the daunting prospect of altering 230+ SQL Server 2005 Reporting Services (SSRS) reports in VS2005, or at least assigning the task to someone. Knowing that SSRS report files are just XML, I suspected that there was an easier way. I had been looking for an excuse to do something with
Powershell and this was it.

After a quick inspection of a few files I was able to build the script below to make the necessary changes to avoid the Print Layout bug. There's definitely room for refactoring because the below script is fairly procedural. But time trumped elegance and Powershell saved a lot of time. The only problem I encountered was that my new elements were being created with an empty xmlns attribute. I'd seen this problem before, but couldn't remember the resolution initially. Google reminded me that I needed to add the new elements to the same namespace as the existing XML document.


$outputDir = "C:\[output directory]\"
$files = Get-ChildItem C:\[path to reports]\*.* -include *.rdl
Write-Host("Found " + $files.Length + " files in " + $files[0].DirectoryName + " directory.`n`r")
foreach ($f in $files)
{
$numberOfModifications = 0
Write-Host($f)
$fileContents = Get-Content $f
$xdoc = New-Object -TypeName System.Xml.XmlDocument
$xdoc.LoadXml($fileContents)

Write-Host("`tBody element")
# Determine whether Body.Style element exists. Apparently Body always exists.
if($xdoc.Report.Body.Style -eq $null)
{
Write-Host("`t`tCreating Body.Style element.")
$styleNode = $xdoc.CreateElement("Style","http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition")
$xdoc.Report.Body.AppendChild($styleNode)
}
else
{
Write-Host("`t`tBody.Style element found.")
$styleNode = $xdoc.Report.Body.Style
}

if(($styleNode -ne $null) -and ($styleNode.BackgroundColor -eq $null))
{
# Note that it's extremely important to qualify the namespace when adding a new element
$xn = $xdoc.CreateElement("BackgroundColor","http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition")
$xn.set_InnerXml("White")
$styleNode.AppendChild($xn)
# $xdoc.Save($outputDir + $f.Name)
$numberOfModifications += 1
}
else
{
Write-Host("`t`tNo modifications necessary.")
}

$styleNode = $null
$xn = $null

#
# PageHeader
#
Write-Host("`tPageHeader element")
if($xdoc.Report.PageHeader -ne $null)
{
$headerNode = $xdoc.Report.PageHeader

# Determine whether PageHeader.Style element exists.
if($headerNode.Style -eq $null)
{
Write-Host("`t`tCreating PageHeader.Style element.")
$styleNode = $xdoc.CreateElement("Style","http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition")
$headerNode.AppendChild($styleNode)
}
else
{
Write-Host("`t`tPageHeader.Style element found.")
$styleNode = $headerNode.Style
}

if(($styleNode -ne $null) -and ($styleNode.BackgroundColor -eq $null))
{
# Note that it's extremely important to qualify the namespace when adding a new element
$xn = $xdoc.CreateElement("BackgroundColor","http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition")
$xn.set_InnerXml("White")
$styleNode.AppendChild($xn)
$numberOfModifications += 1
}
else
{
Write-Host("`t`tNo modifications necessary.")
}
}
else
{
Write-Host("`t`tPageHeader element not found.")
}

$styleNode = $null
$xn = $null


#
# PageFooter
#
Write-Host("`tPageFooter element")
if($xdoc.Report.PageFooter -ne $null)
{
$footerNode = $xdoc.Report.PageFooter

# Determine whether PageHeader.Style element exists.
if($footerNode.Style -eq $null)
{
Write-Host("`t`tCreating PageFooter.Style element.")
$styleNode = $xdoc.CreateElement("Style","http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition")
$footerNode.AppendChild($styleNode)
}
else
{
Write-Host("`t`tPageFooter.Style element found.")
$styleNode = $footerNode.Style
}

if(($styleNode -ne $null) -and ($styleNode.BackgroundColor -eq $null))
{
# Note that it's extremely important to qualify the namespace when adding a new element
$xn = $xdoc.CreateElement("BackgroundColor","http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition")
$xn.set_InnerXml("White")
$styleNode.AppendChild($xn)
$numberOfModifications += 1
}
else
{
Write-Host("`t`tNo modifications necessary.")
}
}
else
{
Write-Host("`t`tPageFooter element not found.")
}

$styleNode = $null
$xn = $null


# Done with this iteration.
if($numberOfModifications -gt 0)
{
Write-Host("`tSaving file.")
$xdoc.Save($outputDir + $f.Name)
}
else
{
Write-Host("`tNo file changes necessary.")
}
$xdoc = $null
}

Tuesday, January 16, 2007

ButtonCollection iteration and PostBacks using WatiN

I've been playing with the .NET 2.0 WatiN framework and ran into a problem that might be of interest to someone.

My application under test is .NET 1.1. I was trying to click through a series of buttons in a DataGrid and also click 'OK' on the confirmation dialog that would appear after the DataGrid
button was clicked. I was generally only able to go through this process once, though, before receiving the following exception:

System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)).

My code to loop through the DataGrid buttons at the time was as follows:

ButtonCollection collection = ie.Buttons.Filter(Find.ByValue("Delete"));
if (collection.Length > 0)
{
foreach (Button button in collection)
{
//ConfirmDialogHandler confirmDialogHandler = new ConfirmDialogHandler();

using (new UseDialogOnce(ie.DialogWatcher, new SimpleJavaDialogHandler(false)))
{
button.Click();
//button.ClickNoWait();
//confirmDialogHandler.WaitUntilExists();
//confirmDialogHandler.OKButton.Click();
//ie.WaitForComplete();
}
}
}

As you can see from the commented-out code I also tried the ConfirmDialogHandler as described in this email.


I finally got my concept to work by using this code:

while (ie.Buttons.Filter(Find.ByValue("Delete")).Length > 0)
{
Button button = ie.Buttons.Filter(Find.ByValue("Delete"))[0];
using (new UseDialogOnce(ie.DialogWatcher, new SimpleJavaDialogHandler(false)))
{
button.Click();
}
}


I assume that my problem was related to the just-clicked button no longer being part of the original ButtonCollection after PostBack and that this created some type of concurrency issue somewhere in the ASP.NET / WatiN innards.

Hope that this helps someone.

Friday, July 7, 2006

"Too Much Persistence"

This problem regarding an ASP.NET 1.1 app, written by another developer, recently came my way:

After searching for a person to add to the [List], it does not successfully perform any subsequent searches.

It's like it is storing part of the old search and that is why the new one isn't working. It does not reset unless you log out of ABC Online completely and go back in. Even after that, you have to keep logging all the way out and back in to do more than one search.


I realized that this matter probably involved ASP.NET Session because the problem was resolved by logging out of the app which destroyed the user's session. From scant past experience I also knew that this app made generous use of session.

I fired up the app and did indeed receive non-deterministic results. If I performed a wide open search with no criteria I would receive a result set of 800 rows. If I specified a last name, I would get back a few rows, which was expected behavior. If I then removed all search criteria and searched again I would not receive 800 rows but instead would receive the previous search results of just a few rows. If I then specified another criteria, such as first name, I received even more confusing and inconsistent results. I ran the Oracle procedure and it returned the expected results. Hmmmm. Time to crack open the code.
userSearchAdapter.SelectCommand.Parameters("I_DIVISION_ID").Value = divisionID
If userType.Length > 0 Then
userSearchAdapter.SelectCommand.Parameters("I_USER_TYPE").Value = userType
End If
If firstName.Length > 0 Then
userSearchAdapter.SelectCommand.Parameters("I_FIRST_NAME").Value = firstName
End If
If lastName.Length > 0 Then
userSearchAdapter.SelectCommand.Parameters("I_LAST_NAME").Value = lastName
End If
If title.Length > 0 Then userSearchAdapter.SelectCommand.Parameters("I_TITLE").Value = title
End If
UserSearchDataSet.Clear()
userSearchAdapter.Fill(UserSearchDataSet)

The code above came from a search method that's part of a search object that is stored in session. The search object is instantiated on first use. I could see from the call to DataSet.Clear that the results were being reset before each call to the Fill method. Setting some debug breakpoints and watches confirmed this, but unexpected results were returned from the database call so stale data wasn't the issue. Observing the values of the OracleParameters finally revealed the problem.

The values of the parameters were only set if the length of its corresponding argument was greater than 0. This meant that once the value of a parameter was set, it wouldn't be reset to null. It could only be set to another value with a length greater than 0. Because this search object was stored in session these parameter values persisted across postbacks. This logic bug explained why searches produced expected results until a user tried to clear search criteria and initiate a new, fresh search.

My solution was predictably overkill. (Bite me- I like to be thorough.) I stuck the following just above the first line of the code above.

' Because this object is persisted in session, its parameters values are constant between postbacks. This characteristic
' causes non-deterministic results because the parameters values, once set, were never unset due to the if logic
' surrounding each parameter. The For loop below resets the values of each parameters before a search so that each
' search begins with a fresh set of parameters.
For Each param As OracleClient.OracleParameter In userSearchAdapter.SelectCommand.Parameters
If param.Direction = ParameterDirection.Input AndAlso Not param.Value Is Nothing AndAlso Not param.Value.Equals(System.DBNull.Value) Then
param.Value = System.DBNull.Value
End If
Next


I fault not the developer but the folks that supposedly QA'd this application. I suppose that they just performed one search and concluded that this functionality was perfect. This application has been in production for almost 6 months and it has taken them this long to discover the problem.

Wednesday, April 19, 2006

Test with lots of data

I forgot to do this and it came back to bite me after deploying an ASP.NET 1.1 web application with a tabbed interface. Each tab on the page main.aspx contains a derived DataGrid with different columns. The DataGrids are created using the factory pattern based on which tab is active and currently displayed. The DataGrid that is returned from the factory is set to a page level DataGrid object. Testing proved that the user could navigate from tab to tab and page within the DataGrids. After deployment, however, ArgumentOutOfRangeExceptions were sometimes thrown after certain combinations of tab navigation and paging. Very quickly I realized that I needed to reset the DataGrid's CurrentPageIndex to zero after navigating to a new tab. This was necessary because the page DataGrid object persisted the CurrentPageIndex between tab clicks. More specifically, a condition could exist where Tab 1 had a DataGrid with 2 pages and Tab 2 had a DataGrid with 10 pages. If a user navigated to page 5 of Tab 2 and then back to Tab 1 then an exception was thrown because Tab 1 didn't have a page 5.

The production environment had at least 10 times the amount of data than our development environment. If I had better prepared the development environment to mirror production I wouldn't have had this post-deployment problem.