OdeToCode IC Logo

Update for the Visual Studio Items Sorting Script

Thursday, March 8, 2007 by scott

Alexander Groß has a sharp looking blog, and he posted an enhanced version of my sort-vsItems PowerShell script. The update is highly recommended, particularly if you have WinFX templates installed. See his post: "Sorting Visual Studio's 'Add New Item' Dialog"

Alexander also has an RDP/Terminal Service Port Configurator for Windows Mobile. I've been frustrated by the fact that the mobile remote desktop client doesn't understand the servername:port syntax, but this utility is a good workaround.

Pluralsight News

Monday, February 19, 2007 by scott

Fritz broke the news on Friday. I've joined the esteemed staff of Pluralsight!

pluralsight logo

What's Wrong With This Code (#12)

Tuesday, February 13, 2007 by scott

Joe Developer has been fired. Sacked. Terminated. Dismissed. Booted. "Dis-employed", if you will.

Before Joe left, he started to write one last piece of code. He was asked to write an HTTP Module in ASP.NET that will log some information, and will include the date and time when the application last started running. He learned his lesson in last week's misadventure (too little, too late), and made his DateTime field static, like so:

using System;
using System.Web;

public class LoggingModule : IHttpModule
{
    
public void Init(HttpApplication context)
    {
        startTime =
DateTime.Now;
    }

    
public void Dispose()
    {
    }

    
static DateTime startTime;
}

Joe's replacement, Estelle Hertz, has to finish the module Joe started. Estelle thinks Joe was off to a pretty good start and adds some event handlers, some logging code, and checks in her changes.

The testers are finding that the "start time" recorded by her module is drifting, but the event logs show the application is definitely not restarting.

Did Joe leave a bug behind?

Windows Workflow Foundation and ASP.NET

Sunday, February 11, 2007 by scott

My latest article shows how to drive an ASP.NET web form using a state machine workflow. The sample is based loosely on the Ordering State Machine smart client sample in the SDK, but adds a few real world requirements.

Sorting the Visual Studio "Add New Item" Dialog with PowerShell

Saturday, February 10, 2007 by scott

The items in the "Add New Item" dialog box of Visual Studio appear in an arbitrary order. After a bit of sleuthing, I put together a brute force Powershell script to sort my items alphabetically. Now "Code File" appears near "Class", and I can always find "XML File" near the bottom of the dialog.

sorted items in vs2005

SortOrder, and my sanity, is restored.

What follows is the script. Download sort-vsItems.ps1. Let me caveat this script by saying it has only been tested on two machines. If you have any problems, do let me know.

# sort-vsItems
# scott@OdeToCode.com
# Modified by AlexanderGross at gmx dot de
#
# Use at your own risk!
# Script will make a backup of each template just in case.

# vjslib for .zip support.
[System.Reflection.Assembly]::Load("vjslib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") | out-null

# Get list of VS installed templates.
$installDir = [System.IO.Path]::Combine((gp HKLM:Software\Microsoft\VisualStudio\8.0).InstallDir, "ItemTemplates")
$templateFiles = gci -recurse $installDir | ? {$_.extension -eq ".zip"}

# Append list of custom templates.
$installDir = [System.IO.Path]::Combine((gp "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders").Personal, "Visual Studio 2005\Templates\ItemTemplates")
$templateFiles += gci -recurse $installDir | ? {$_.extension -eq ".zip"}

# Sort all templates by filename.
$templateFiles = $templateFiles | sort name

$i = 1
$count = 0
$tmpDir = new-item ([System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())) -type directory
$buffer = new-object System.SByte[] (8192)

# Iterate through all template files.
foreach($templateFile in $templateFiles)
{
write-host "Processing" $templateFile.FullName

# Extract all files (no methods available to modify zip in place).
$zip = new-object java.util.zip.ZipFile($templateFile.FullName)
$entries = $zip.entries()
while($entries.hasMoreElements())
{
$zipEntry = $entries.nextElement()

# Ensure output directory exists.
$filename = [System.IO.Path]::Combine($tmpDir.FullName, $zipEntry.getName())

# $zipEntry.isDirectory() does not work for Microsoft zips (e.g. Web\CSharp\WinFxServiceItemTemplate.zip).
$directory = [System.IO.Path]::GetDirectoryName($filename)
if ([System.IO.Directory]::Exists($directory) -ne $true)
{
mkdir $directory | out-null

# In case the zip tells us it's a directory entry, skip the entry to prevent exceptions.
if ($zipEntry.isDirectory())
{
continue
}
}

$in = $zip.getInputStream($zipEntry)
$out = new-object java.io.FileOutputStream($filename)

while(($count = $in.read($buffer, 0, $buffer.Count)) -gt 0)
{
$out.write($buffer, 0, $count)
}

$out.Close()
$in.Close()
}
$zip.Close()

# Tweak the sort order element.
$vst = gci -recurse $tmpDir | ? {$_.extension -eq ".vstemplate"}
if ($vst -eq $null)
{
# The zip file does not contain a vstemplate.

# Clean temporary directory for the next template file.
del $tmpDir\* -force -recurse
continue
}

$xmlDoc = new-object System.Xml.XmlDocument
$xmlDoc.Load($vst.FullName)
if ($xmlDoc.VSTemplate.TemplateData.SortOrder -ne $null)
{
# Sort by zip name. Sort order must be a multiple of 10.
$xmlDoc.VSTemplate.TemplateData.SortOrder = ($i++ * 10).ToString()

# Sort by item name in Visual Studio.
# Uncomment this line if you want to let Visual Studio sort by item name.
# $xmlDoc.VSTemplate.TemplateData.SortOrder = "10"

$xmlDoc.Save($vst.FullName)
}

# Backup existing zip file.
$backupName = $templateFile.FullName + ".bak"
if(test-path $backupName)
{
# Remove previous backups.
remove-item $backupName
}
move-item $templateFile.FullName $backupName

# Zip up modified version.
$zip = new-object java.util.zip.ZipOutputStream(new-object java.io.FileOutputStream($templateFile.FullName))
$files = gci -recurse $tmpDir
foreach($file in $files)
{
if ($file.Attributes -contains "Directory")
{
# Subfolders are created automatically with files residing in subfolders.
continue
}

# Create a file entry.
# Replacing the last backslash from $tmpDir.FullName is crucial, the zips would work with any other
# zip editor but Visual Studio doesn't like files with a leading backslash (though one doesn't see
# it in WinZip).
$zipEntry = new-object java.util.zip.ZipEntry($file.FullName.Replace($tmpDir.FullName + "\", ""))
$zip.putNextEntry($zipEntry)
$in = new-object java.io.FileInputStream($file.FullName)
while(($count = $in.read($buffer, 0, $buffer.Count)) -gt 0)
{
$zip.write($buffer, 0, $count)
}
$in.close()
$zip.closeEntry()

}
$zip.close()

# Clean temporary directory for the next template file.
del $tmpDir\* -force -recurse
}

del $tmpDir -force -recurse

write-host "Running Visual Studio to refresh templates"
$vstudio = (gp HKLM:Software\Microsoft\VisualStudio\8.0).InstallDir
& $vstudio\devenv /setup

Powershell is beauty!

What's Wrong With This Code? (#11)

Wednesday, February 7, 2007 by scott
Joe Developer is tasked with displaying the start date of his ASP.NET application. Joe thinks he'll just add a DateTime field to global.asax and initialize the field during the Application_Start event.

<%@ Application Language="C#" %>

<script runat="server">

    void Application_Start(object sender, EventArgs e)
    {
        StartDateTime =
DateTime.Now;
    }
    
    
public DateTime StartDateTime;
          
</script>

Whenever Joe needs to display the application start date, he accesses this public field.

Joe doesn't know what is wrong, but he is sure of one thing – the date that is displaying on his pages is not the date when the application started. Can you help out Joe one more time?

Sneak Peek: ASP.NET and Windows Workflow

Monday, February 5, 2007 by scott

I’ve been working on an article that will show how to use a Windows Workflow state machine to drive logic behind an ASP.NET web form. Generally, my approach in articles is to use just enough code to reveal a specific concept. I don’t use heavy abstractions or include the safeguards and proven practices we need for production applications.

There is delicate balance to strike when devising sample code, and you can never please everyone. Simple code will find itself cut and pasted into situations where it shouldn’t exist, and the person who has to fix the situation will curse you for being a fool. On the other hand, complex code is frustrating to the person who doesn’t want to tear apart three software layers just to see how to kick start a workflow in ASP.NET.

For the article I’m working on, I could cram 15-20 lines of code into a Page_Load event. I started down this road, in fact, but I began to feel dirty. So dirty, I took a hot shower and came up with something like this:

Workflow and ASP.NET

Now the ASP.NET web form doesn't know anything about Windows Workflow - it only sees a service that interacts with domain objects. Between this service and the WF runtime is a mediator that uses knows how to run workflows and harvest workflow results. A second  service - a transaction service, commits units of work inside the same transaction as the WF persistence and tracking services. The end result is a unit-testable architecture, and relatively clean event handlers in ASP.NET code.  

protected void shipOrderButton_Click(object sender, EventArgs e)
{
    
int orderId = GetSelectedOrderId();
    
Check.IsNotTrue(orderId < 1, "Could not find selected order ID");

    
Order selectedOrder = OrderService.Instance.GetOrderById(orderId);
    
Check.IsNotNull("selectedOrder", "Could not fetch order " + orderId);

    
OrderService.Instance.ShipOrder(selectedOrder);
    UpdateView();
}

The result is a more advanced article where the reader will need to understand some of the WF basics before digging through the sample application. I might be leaving out beginners, but I feel the complexity is justified. Now I just need to finish the article, as it is one week behind schedule. It's easy to push back deadlines since I set the schedule!