Updated Again: Setting up Source Server for TFS Builds (v1.2)#

I've just published another update to my guide on Setting up Source Server for TFS Builds, since the Debugging Tools for Windows now has built-in support for Team Foundation Server. So that means: no more third party downloads and an even simpler installation procedure! I've also updated the document to reflect all applicable versions of the tools you'll be using: Visual Studio 2005 and 2008, as well as Team Foundation Server 2005 and 2008.

Because I have been troubleshooting Source Server issues a few times as well, I also added a chapter on how to find out what's going on inside the Source Server indexing and what might be going wrong. Note that there is some Perl going on in there, so avert your eyes if you can't handle the look and feel of it (I know I can't) :-)

For the full setup instructions, please refer to the original post on Setting up Source Server for TFS Builds.

Oh, and finally, my homie Pieter also posted a more detailed guide on how to set Source Server support up inside the Team Build script - and what's more important, what the invaluable benefit is of having a central reusable build script. Great job Pieter!

Tuesday, July 08, 2008 8:57:07 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Updated: Setting up Source Server for TFS Builds#

Just a quick note to let you know that I've updated my guide on Setting up Source Server for TFS Builds, since I just found out that there is an issue with Build Definitions that contain spaces. The fix is fairly easy though:

  • In TFIndex.cmd (on the build server), remove the quotes around the %1 argument for SYMBOLS:
@call "%~dp0SSIndex.cmd" -SYSTEM=TF -SYMBOLS="%1" %*
@call "%~dp0SSIndex.cmd" -SYSTEM=TF -SYMBOLS=%1 %*
  • In the Team Build Script (in Source Control), add XML-escaped quotes around the $(BinariesRoot) argument:
<Exec Command="&quot;C:\Program Files\Debugging Tools for Windows\sdk\srcsrv\TFIndex.cmd&quot; $(BinariesRoot)"
      WorkingDirectory="$(SolutionRoot)" />
<Exec Command="&quot;C:\Program Files\Debugging Tools for Windows\sdk\srcsrv\TFIndex.cmd&quot; &quot;$(BinariesRoot)&quot;"
      WorkingDirectory="$(SolutionRoot)" />

For the full setup instructions, please refer to the original post on Setting up Source Server for TFS Builds.

Wednesday, April 30, 2008 11:20:47 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Setting up Source Server for TFS Builds#

[Update 30/04/2008] Fixed issue for Build Definition names that contain spaces.

[Update 08/07/2008] Updated for new version of Debugging Tools for Windows; added Troubleshooting chapter.

[Update 05/08/2008] Updated "srcsrv.ini" file contents for new version of Source Server script (does not include cmd.exe anymore).

[Update 20/03/2009] Added troubleshooting part for "Illegal characters in path" error.

[Update 30/03/2009] Updated "Illegal characters in path" patch to also include the srcsrv.dll file (in addition to symsrv.dll).

I've read about symbol servers and source servers before (in John Robbins' excellent Debugging Microsoft .NET 2.0 Applications book, for example, and also due to the recent news that Visual Studio 2008 will support source-code debugging of the .NET Framework Libraries), but never really got around to trying them out since they seemed pretty complex to set up. However, I've recently set up a source server on a Team Foundation build server (twice!), and my eyes have now gone wide open: anybody who references assemblies built by those TFS build servers can now seamlessly debug them.

When an assembly is "source server-enabled", the pdb file will contain the full path and the exact version of the file in source control that was used to build that assembly. If the debugger then enters a method, Visual Studio automatically downloads that correct file, places it in a local cache, and opens it for debugging. This is super sweet!

Now the hard work is already done and available in the Debugging Tools for Windows, but there are some extra steps to take if you want to integrate this in a real Team Build (i.e. on the TFS build server instead of on a local machine) and it also lacks some additional information to get everything working. So I put together a full document that covers all aspects of setting up a source server, modifying the Team Build script, configuring the project files, and finally configuring the development machines.

The full document (PDF) can be downloaded here, and you can read the one-page summary that briefly covers the required steps below. I cannot recommend setting up a source server enough, it's free and takes less than an hour if you simply follow the procedure, and the advantages are - hopefully - obvious.

Setting up Source Server for TFS Builds.pdf (0,98 MB)

Note that this procedure works for both TFS 2005 as TFS 2008, and both VS 2005 as VS 2008.

Installing Source Server on the Build Server

  • Install the Debugging Tools for Windows (at least the "Source Server" component)
  • Install Perl (e.g. ActivePerl)
  • In the tfsindex.cmd file in the Debugging Tools for Windows directory, change the last line to
@call "%~dp0SSIndex.cmd" -SYSTEM=TFS -SYMBOLS=%1 %*
  • In the srcsrv.ini file in that directory, set MYSERVER to the exact URL of your Team Foundation Server (Application Tier)
  • Add the full path to TF.exe (usually "C:\Program Files\Microsoft Visual Studio 8\Common7\IDE" for VS 2005 or "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE" for VS 2008) to the system's Path environment variable
  • Restart the "Team Build Service" (for TFS 2005) or "Visual Studio Team Foundation Build" (for TFS 2008) Windows service
Enabling Source Server in the Team Build Script
  • Add a new target to TFSBuild.proj to be run on "AfterCompile" that calls tfsindex.cmd:
<Target Name="RunSourceServerIndexing">
<Exec Command="&quot;C:\Program Files\Debugging Tools for Windows (x86)\srcsrv\tfsindex.cmd&quot; &quot;$(BinariesRoot)&quot;"
        WorkingDirectory="$(SolutionRoot)" /> </Target> <Target Name="AfterCompile"
        DependsOnTargets="RunSourceServerIndexing" />
Configuring the Project Files
  • Set the Debug Info in Project Properties / Build / Advanced to "full" (Debug configuration) or "pdb-only" (Release configuration)
Configuring the Development Machines
  • In Visual Studio / Tools / Options / Debugging / General: check the "Enable source server support" checkbox
  • Create a new text file named srcsrv.ini in the Visual Studio directory (usually "C:\Program Files\Microsoft Visual Studio 8\Common7\IDE" for VS 2005 or "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE" for VS 2008) that contains the exact following text:
[trusted commands] 
TF.exe view
Sunday, December 09, 2007 7:16:12 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Popfly Explorer: Nifty & Smooth!#

Well finally: my good buddy Steven has at last done something useful since arriving at Microsoft Corp. a little over a year ago :-) Although Popfly itself has been getting a lot of good vibe and is certainly a cool playground for mash-ups and showcase for Silverlight, the recently announced Popfly Explorer is something that "clicks" with me a lot more. In other words: something I'll actually use :-)

Basically, Popfly Explorer is a tool window in Visual Studio (all editions, even Express!) that allows you to connect to a Microsoft server to upload your projects, create web sites and even host them directly on their servers, and build a community of friends where you can share code and comment on each other's stuff. So for me, the fun part is where you can directly upload your current solution to Popfly, switch to another machine, download it again and continue working. It's sort of like a Shelveset in Team Foundation Server, but then without the Team Foundation Server. And for free.

When he first described this to me, I was like "hey, cool, it's like CodePlex LightTM(R)(C) or something". And then Steven was all "yeah dude that's right". And I was like "yo wicked bro". And he was all like "yeah, it's so totally sweet". And then I tried it, and it actually worked, and everything went smooth, and I was all "w000000t"!

Anyway. Soma announced it a few days ago on TechEd EMEA, Steven posted a quick overview on Popfly Explorer (by the way, check out the cool picture of Steven as a kid on the logon screen!) and Dan Fernandez followed up with a bigger post describing how to build and publish web sites with Popfly Explorer. I encourage you to check it out, yo! Download Popfly Explorer Beta and get to the party!

Blog | Programming | .NET | VS.NET
Saturday, November 10, 2007 8:07:56 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

GAS07: Renaming the solution, signing projects and showing documentation#

[This is episode 7 of the Guidance Automation Series]

It's been a while since the last episode, but I've finally found the time to finish the (long awaited) grand finale of the Guidance Automation Series.

Last time, we looked at class generation using the powerful T4 text templating engine. Armed with all the knowledge up to here, it's already perfectly possible to build an entire guidance package that allows the developer to create Enterprise Library Application Blocks with a minimal amount of effort. As I said in the introduction to the Guidance Automation Series, I'm not going to build that guidance package here. I'll just show you some more tricks you can teach your guidance package to perform... This time, we'll be adding a strong name to our assemblies by signing it with a key and see how we can rename the solution's file name.

Renaming The Solution

We'll start off with the relatively easy task of renaming the solution. Although the developer has already specified a solution name, in a lot of organizations it's required that the solution also adheres to a certain naming convention. And possibly, this depends on some fields that the user can define in the wizard belonging to the Guidance Package, such as a namespace. The only problem is, our Guidance Package doesn't kick in until after the developer has specified a name for the solution file - and now it's obviously too late to rename it. With a little trick, however, it's perfectly possible to change the solution name, e.g. to ApplicationBlockNamespace.ApplicationBlockName.sln in our Guidance Package for Enterprise Library application blocks.

To do this, we will need a custom action class that performs this magic. We can create a custom action by deriving from the Microsoft.Practices.RecipeFramework.Action class. An action can take inputs and create outputs by defining properties that are decorated with the Input or Output attributes:

public class RenameSolutionAction : Action
{
    /// <summary>
    /// The new name of the solution.
    /// </summary>
    private string m_NewSolutionName;

    /// <summary>
    /// Gets or sets the new name of the solution.
    /// </summary>
    [Input(Required = true)]
    public string NewSolutionName
    {
        get
        {
            return m_NewSolutionName;
        }
        set
        {
            m_NewSolutionName = value;
        }
    }

    /// <summary>
    /// Executes the action.
    /// </summary>
    public override void Execute()
    {
    }

    /// <summary>
    /// Performs an undo of the action.
    /// </summary>
    public override void Undo()
    {
    }
}

At this point, our custom action doesn't really do anything but you can see that it overrides the Execute and Undo methods that are called when the action is run and when it needs to be undone.

The actual work of renaming the solution can now be performed in the Execute method by using the EnvDTE objects of the Visual Studio SDK. In our case, we'll perform the rename by simply doing a "Save As" of the solution using the new name, and then deleting the original solution file and its associated suo file:

public override void Execute()
{
    DTE vs = this.GetService<DTE>(true);

    string newFilename = this.NewSolutionName;
    string originalSolutionPath = (string)vs.Solution.Properties.Item("Path").Value;
    string originalSolutionDir = Path.GetDirectoryName(originalSolutionPath);

    // Check if it is an absolute or relative path.
    if (!Path.IsPathRooted(this.NewSolutionName))
    {
        // Relative path from the current solution root.
        newFilename = Path.Combine(originalSolutionDir, this.NewSolutionName);
    }

    // Make sure the destination directory exists.
    Directory.CreateDirectory(Path.GetDirectoryName(newFilename));

    // Save the current solution as the new file name.
    vs.Solution.SaveAs(newFilename);

    // Delete the old solution.
    File.Delete(originalSolutionPath);
    string suoFile = Path.GetFileNameWithoutExtension(originalSolutionPath) + ".suo";
    string suoFilePath = Path.Combine(originalSolutionDir, suoFile);
    File.Delete(suoFilePath);
}

Guidance Automation Tip #3: The vs.Solution.FileName or vs.Solution.FullName properties do not return a value while the solution is being created. You can use "string solutionPath = (string)vs.Solution.Properties.Item("Path").Value;" to get the full path of the solution.

Now that our action is created, we can register it in the Guidance Package's main XML configuration file in the <Actions> section:

<Action Name="RenameSolution" Type="JelleDruyts.EnterpriseLibraryGuidance.Actions.RenameSolutionAction, JelleDruyts.EnterpriseLibraryGuidance">
  <Input Name="NewSolutionName" RecipeArgument="FinalSolutionName" />
</Action>

The action takes an input argument named NewSolutionName (as defined in the RenameSolutionAction class), which is taken from a Recipe Argument named FinalSolutionName. This argument is again a "derived" argument, which is composed of the application block namespace and name through an evaluator:

<Argument Name="FinalSolutionName">
  <ValueProvider Type="Evaluator" Expression="$(ApplicationBlockNamespace).$(ApplicationBlockName).sln">
    <MonitorArgument Name="ApplicationBlockNamespace" />
    <MonitorArgument Name="ApplicationBlockName" />
  </ValueProvider>
</Argument>

Signing The Projects

As a last step, we want to enable the developer to sign the generated projects with a strong-name key. This means we will need to generate a key-file dynamically (by using the sn.exe tool that ships with the .NET SDK), and that we need to configure the generated projects to use the key to sign the assemblies.

In our specific case, signing the Enterprise Library Application Block assemblies has an extra risk: this will only work if Enterprise Library itself is signed because a signed assembly cannot reference an unsigned assembly. Because Enterprise Library is not signed by default, we will need to make this step optional so the developer can choose whether or not to sign the projects. This means we will need an extra argument (that defaults to false) and a field in the wizard:

<Argument Name="EnableStrongNaming" Required="true" Type="System.Boolean">
  <ValueProvider Type="Evaluator" Expression="false" />
</Argument>
...
<Field ValueName="EnableStrongNaming" Label="Enable strong-naming">
  <Tooltip>Only enable strong naming if you have a build of Enterprise Library that is itself strong-named.</Tooltip>
</Field>

Now the trick to signing the projects when they are generated is actually quite simple: there are two settings in the C# project file that are required: SignAssembly must be set to true, and AssemblyOriginatorKeyFile must be set to the path to the key file. We can do this by using recipe arguments in the .csproj template files as such:

<SignAssembly>$EnableStrongNaming$</SignAssembly>
<AssemblyOriginatorKeyFile>$AssemblyOriginatorKeyFile$</AssemblyOriginatorKeyFile>

The developer has already specified the EnableStrongNaming argument through a field in the wizard. Now the AssemblyOriginatorKeyFile needs to point to a valid path to a key file in case strong naming is enabled, or it should be an empty string if strong naming is not enabled. This can be performed by using a custom value provider that checks the EnableStrongNaming argument and determines which string to return: either a string containing the path to the key-file, or an empty string. I won't go into the details here, since Value Providers were already covered in a previous article in this series, but I just want to stress one important point when developing them:

Guidance Automation Tip #4: When writing a Value Provider, returning null means "don't use this value".

This means that if you would return null in the Value Provider that provides a value for the AssemblyOriginatorKeyFile argument and the developer chose not to strong-name the assemblies, the $AssemblyOriginatorKeyFile$ would not get replaced at all in any template! If you want the argument to be replaced with an empty string, be sure to return an empty string from the Value Provider as well:

if (!enabled)
{
    newValue = string.Empty; // Not null!
}
else
{
    newValue = string.Format(@"..\{0}", keyFileName);
}

Now that we have a Value Provider for the path to the key-file that also works when the developer chose not to use strong-naming, we can register the following recipe arguments to define the KeyFileName (again based on the Application Block namespace and name) and the AssemblyOriginatorKeyFile arguments:

<Argument Name="KeyFileName">
  <ValueProvider Type="Evaluator" Expression="$(ApplicationBlockNamespace).$(ApplicationBlockName).snk">
    <MonitorArgument Name="ApplicationBlockNamespace" />
    <MonitorArgument Name="ApplicationBlockName" />
  </ValueProvider>
</Argument>
<Argument Name="AssemblyOriginatorKeyFile">
  <ValueProvider Type="JelleDruyts.EnterpriseLibraryGuidance.ValueProviders.KeyFilePathValueProvider, JelleDruyts.EnterpriseLibraryGuidance"
                 KeyFileNameArgument="KeyFileName" EnableStrongNamingArgument="EnableStrongNaming">
    <MonitorArgument Name="KeyFileName" />
    <MonitorArgument Name="EnableStrongNaming" />
  </ValueProvider>
</Argument>

The only remaining work is to actually generate the key file itself and add it to the Visual Studio solution. This can be implemented as a custom action again, in which we will perform the following work:

public override void Execute()
{
    if (this.EnableStrongNaming)
    {
        EnvDTE.DTE vs = this.GetService<EnvDTE.DTE>(true);
        string solutionPath = (string)vs.Solution.Properties.Item("Path").Value;
        string solutionDir = Path.GetDirectoryName(solutionPath);
        string keyFilePath = Path.Combine(solutionDir, this.KeyFileName);

        // Find the .NET SDK directory to execute the sn.exe command.
        string visualStudioDir = Path.GetDirectoryName(vs.Application.FileName);
        string sdkDir = Path.Combine(visualStudioDir, @"..\..\SDK\v2.0\Bin");
        string snPath = Path.Combine(sdkDir, "sn.exe");
        if (!File.Exists(snPath))
        {
            throw new InvalidOperationException("Could not find the Visual Studio SDK directory to execute the sn.exe command.");
        }

        // Make sure the directory exists.
        Directory.CreateDirectory(Path.GetDirectoryName(keyFilePath));

        // Launch the process and wait until it's done (with a 10 second timeout).
        ProcessStartInfo startInfo = new ProcessStartInfo(snPath, string.Format("-k \"{0}\"", keyFilePath));
        startInfo.CreateNoWindow = true;
        startInfo.UseShellExecute = false;
        Process snProcess = Process.Start(startInfo);
        snProcess.WaitForExit(10000);

        // Add the key file to the Solution Items.
        DteHelper.SelectSolution(vs);
        vs.ItemOperations.AddExistingItem(keyFilePath);

        // The AddExistingItem operation also shows the item in a new window, close that.
        vs.ActiveWindow.Close(EnvDTE.vsSaveChanges.vsSaveChangesNo);
    }
}

This custom action uses an input parameter named KeyFileName which is the recipe argument we defined before. We have also passed in the EnableStrongNaming argument to know if we actually need to generate the key file. We then find the path to the .NET SDK by first looking at the application directory of Visual Studio (vs.Application.FileName) and then using a relative path to the SDK. We check for the presence of the sn.exe application, and start a process (without a window to avoid the DOS box from popping up) to generate the key file. When it's done, we add the key file to the solution. Note the special way of adding a file to the "Solution Items" special folder: we're first selecting the solution in Solution Explorer (through the DteHelper class), and then calling the AddExistingItem method with the path to the item. This seems to be the easiest and most robust way of doing this, since the "Solution Items" folder is handled quite differently and it wouldn't be enough to just create a folder with that name. In the end, we also close the active window, because by default the added item is shown in a new window - which doesn't make a lot of sense for a binary key file.

Now that we have all this infrastructure in place, running the Guidance Package with strong naming enabled results in the following solution, properly renamed, containing the strong name key-file and with both projects enabled for signing:

Showing Documentation

One of the nice added features in the latest version of the Guidance Automation Toolkit is the Guidance Navigator, which provides an overview of the currently loaded Guidance Packages and the recipes they provide. It can also show an HTML page containing more information and links to online resources, to provide continuous guidance to developers using the package.

Showing an HTML page in this Guidance Navigator window is as simple as adding the following XML section to the top of the Guidance Package's main XML file, as the first node inside the main <GuidancePackage> tag:

<GuidancePackage xmlns="http://schemas.microsoft.com/pag/gax-core" ...>
  <Overview Url="Templates\Doc\Overview.html" />

The Url points to the HTML page to be shown, of course, and I've chosen to store this in the Templates\Doc directory. When the solution is created or opened, the Guidance Navigator window will show the page as such:

Where Are We

At this point, we've added some custom actions to rename the solution and we've also given the developer the option to sign the assemblies with a strong name.

I think I've covered quite a lot of scenarios already to end the Guidance Automation Series right here. Any additional tips and tricks I discover will of course be posted here, so keep an eye on my blog if you're interested in the Guidance Automation Toolkit!

Download the source code for the current state of the Guidance Package.

Monday, September 18, 2006 7:00:28 PM (Romance Standard Time, UTC+01:00) #    Comments [5]  | 

 

GAS06: Generating Classes#

[This is episode 6 of the Guidance Automation Series]

Last time, we added references to Enterprise Library and made sure the build output was copied to the configuration tool directory. Now to make the application block show up in this configuration tool, it needs to register some classes and commands so that the tool knows how to display it. Without implementing the full works here, let's see how we can already generate some classes that will serve as an excellent starting point to implement the rest of the application block.

Adding Default Classes

Actually, we've already seen how to add classes in the first episode of this series: the AssemblyInfo.cs file was added to the project by including it in a ProjectItem inside the project's vstemplate file and by including it in the C# project file (so that MSBuild will build it). The only extra feature we'll be using here is to change the target file name of each file to include the name of the application block.

To make it possible to add the application block in the configuration tool, we have to define a ConfigurationDesignManager class and register it in the AssemblyInfo.cs file. So we need to update that file and add a bunch more, including a CommandRegistrar, a NodeMapRegistrar and a root settings node. For the purpose of all these classes and how they should be implemented, I'll gladly refer to Mark Seemann's excellent article on "Speed Development With Custom Application Blocks For Enterprise Library" again. All we need to do here is provide the minimum amount of information to get it registered with the configuration tool and leave the rest up to the actual developer of the application block.

So we add all the necessary files to the project template, include them in the C# project file and register them in the vstemplate file as such:

<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$CommandRegistrar.cs">CommandRegistrar.cs</ProjectItem>
<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$ConfigurationDesignManager.cs">ConfigurationDesignManager.cs</ProjectItem>
<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$NodeMapRegistrar.cs">NodeMapRegistrar.cs</ProjectItem>
<ProjectItem ReplaceParameters="true" TargetFileName="$ApplicationBlockName$SettingsNode.cs">SettingsNode.cs</ProjectItem>
<ProjectItem ReplaceParameters="false" TargetFileName="$ApplicationBlockName$SettingsNode.bmp">SettingsNode.bmp</ProjectItem>

Notice the TargetFileName attribute here, to specify a new name for the file so that it includes the name of the application block as it was entered by the user. Also note that we're adding a bitmap file here (without replacing any parameters, to make sure we're not accidentally messing with the binary data), which is also added in the C# project file but then as an embedded resource:

<ItemGroup>
  <EmbeddedResource Include="$ApplicationBlockName$SettingsNode.bmp" />
</ItemGroup>

That's all there is to it. If we now register and run the Guidance Package, we'll get a project that includes all of these files. If we build it (without touching anything), it will automatically get copied into the configuration tool's directory (because of the post-build event), and we can already add the application block to an application directly!

Generating Classes

Now that we already have a root node in the project, suppose we want to add more nodes for the configuration tool. The number of nodes in the application block depends on what the developer has in mind and could even be unknown when the application block is being created. So in stead of adding more classes at the time the solution is unfolded, let's add a command to Visual Studio that allows us to generate a class on-demand. This can be done by adding a RecipeReference to a Visual Studio Template or by adding a separate template for the class in the Templates\Items directory.

  • With a RecipeReference, you have full control over the process: when the recipe is executed, you can choose to show a dialog box prompting for more information or you can go directly to work by executing actions. A drawback is that you also need to include the actual actions to generate the class content and add it to the project, although this isn't all too hard.
  • With a Template Reference, you will always get the "Add new item" dialog box that allows the developer to choose the new class' filename. This can be a drawback if you need to control the filename as well, but this mechanism does make it easier to generate the class and add it to the project.

Since adding the class as a Template Reference is very similar to adding a solution as a Template Reference (like we've already been doing all along), let's implement the new nodes as RecipeReferences. To allow the developer to add dynamically created items to a project, we need to attach a reference to a recipe in that project's vstemplate file. In this case, we'll add the following information to the vstemplate file of the DesignTime project:

<WizardData>
  <Template xmlns="http://schemas.microsoft.com/pag/gax-template" SchemaVersion="1.0">
    <References>
      <RecipeReference Name="AddConfigurationNode" Target="/" />
    </References>
  </Template>
</WizardData>

The References node has been added to register the AddConfigurationNode recipe to the root of the project (specified by the target). If there would be subdirectories, you could scope the target down by setting Target="/Nodes", for example.

The AddConfigurationNode recipe needs to be defined in the package's main xml file as such:

<Recipe Name="AddConfigurationNode">
  <Caption>Configuration Node...</Caption>
  <Description>Adds a Configuration Node class.</Description>
  <HostData>
    <Icon Guid="FAE04EC1-301F-11d3-BF4B-00C04F79EFBC" ID="4542" />
    <CommandBar Name="Project Add" />
  </HostData>
  <Arguments>
    <!-- User Input -->
    <Argument Name="NodeName" Required="true">
      <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.CodeIdentifierStringConverter, Microsoft.Practices.RecipeFramework.Library"/>
    </Argument>
    <!-- Derived Arguments -->
    <Argument Name="CurrentProject" Type="EnvDTE.Project, EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
      <ValueProvider Type="Microsoft.Practices.RecipeFramework.Library.ValueProviders.FirstSelectedProject, Microsoft.Practices.RecipeFramework.Library" />
    </Argument>
    <Argument Name="TargetFile">
      <ValueProvider Type="Evaluator" Expression="$(NodeName).cs">
        <MonitorArgument Name="NodeName" />
      </ValueProvider>
    </Argument>
    <Argument Name="TargetNamespace">
      <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.NamespaceStringConverter, Microsoft.Practices.RecipeFramework.Library"/>
      <ValueProvider Type="Evaluator" Expression="$(CurrentProject.Properties.Item('DefaultNamespace').Value)" />
    </Argument>
  </Arguments>
  <GatheringServiceData>
    <Wizard xmlns="http://schemas.microsoft.com/pag/gax-wizards" SchemaVersion="1.0">
      <Pages>
        <Page>
          <Title>Configuration Node</Title>
          <Fields>
            <Field ValueName="NodeName" Label="Node Name" InvalidValueMessage="Must be a valid .NET identifier (e.g. it shouldn't contain spaces or special characters)." />
          </Fields>
        </Page>
      </Pages>
    </Wizard>
  </GatheringServiceData>
  <Actions>
    <Action Name="GenerateClass" Type="Microsoft.Practices.RecipeFramework.VisualStudio.Library.Templates.TextTemplateAction, Microsoft.Practices.RecipeFramework.VisualStudio.Library"
				Template="Text\ConfigurationNode.cs.t4" >
      <Input Name="TargetNamespace" RecipeArgument="TargetNamespace" />
      <Input Name="NodeName" RecipeArgument="NodeName" />
      <Output Name="Content" />
    </Action>
    <Action Name="AddClass" Type="Microsoft.Practices.RecipeFramework.Library.Actions.AddItemFromStringAction, Microsoft.Practices.RecipeFramework.Library"
				Open="true">
      <Input Name="Content" ActionOutput="GenerateClass.Content" />
      <Input Name="TargetFileName" RecipeArgument="TargetFile" />
      <Input Name="Project" RecipeArgument="CurrentProject" />
    </Action>
  </Actions>
</Recipe>

The recipe definition starts off with the regular caption and description elements. The HostData section defines where the recipe should be shown in Visual Studio; in this case we add it only to the project's "Add" context menu (where the "Add new item" and "Add existing item" menus also live) and supply it with an appropriate icon from the C# Project dll again. Then, there's a regular argument that the developer can fill in through the wizard, but the more interesting part is the definition of the other arguments: we're using a new type of value provider that returns the currently selected project in Visual Studio (so we can add the new class to it). There are also some more uses for the ExpressionEvaluatorValueProvider: a simple one to define the filename based on the class name, and a pretty complex one that finds the default namespace from the current project by drilling down into its properties (the nested expression is evaluated at runtime and executed through reflection). Finally, the actions section defines two distinct stages: a first one to generate the class content by executing the TextTemplateAction, which transforms a T4 text template using a quite advanced text processing engine. Afterwards, the generated class content is added to the currently selected project by using the AddItemFromStringAction.

Finally, the template itself looks like this:

<#@ template language="C#" #>
<#@ assembly name="System.dll" #>
<#@ property processor="PropertyProcessor" name="TargetNamespace" #>
<#@ property processor="PropertyProcessor" name="NodeName" #>
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Text;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Configuration.Design;

namespace <#= this.TargetNamespace #>
{
    internal sealed class <#= this.NodeName #> : ConfigurationNode
    {
    }
}

This templating syntax looks quite a lot like ASP.NET, so creating and understanding these templates should be relatively straight-forward. We first declare some properties that are inputs from the recipe declaration above. These will actually become strongly-typed properties on the page template object, so we can refer to them later on by using this.NodeName, for example. At this point, the generated class is extremely simple, but you can actually perform very powerful code generation with this T4 templating engine by inlining arbitrary C# or VB.NET code within these ASP.NET-like tags that will drive the code generation process.

Notice that you also need to register the assemblies you require for the code generation process. In this case, we just register the System.dll assembly. If, for example, we needed to use the Visual Studio automation dll (EnvDTE.dll) in the template, we would need to add the following declarations:

<#@ assembly name="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PublicAssemblies\EnvDTE.dll" #>
<#@ import namespace="EnvDTE" #>

Failing to do so will give you the following nasty error: "CS0246: Compiling transformation: The type or namespace name 'EnvDTE' could not be found (are you missing a using directive or an assembly reference?)"

With this in place, it's now possible to add a new configuration node through the project's context menu:

Where Are We

At this point, we know how to add default classes to a generated project that have a customized name. Furthermore, we've touched the surface on the very powerful T4 text templating engine that's part of the Guidance Automation framework. Next time, we'll define a custom action that generates a strong name key and configure the projects to automatically be signed with it. We'll also automatically change the solution's file name to something more meaningful.

Download the source code for the current state of the Guidance Package.

Monday, July 03, 2006 8:39:13 PM (Romance Standard Time, UTC+01:00) #    Comments [4]  | 

 

GAS05: Tuning the C# projects#

[This is episode 5 of the Guidance Automation Series]

Last time, we left off with two empty projects that had a project reference between them. If you're still not too thrilled by all that, let's take it one step further: we'll add binary references to Enterprise Library and make sure that the project output is always copied to the Enterprise Library Configuration Tool directory (otherwise, the application block won't show up in the configuration tool). The first one involves knowing where to find those references, the second one involves executing a post-build command that copies the project output to the proper location.

Adding Binary References

Adding a binary reference in itself is pretty straight-forward. The project references are stored in the C# project file (which is an MSBuild file) and can therefore easily be added in the project's template. The only minor difficulty is that we need to provide the hint-path, i.e. the physical location where the assemblies can be found.

In this case, we'll be adding references to Enterprise Library assemblies, so we need to know the directory where Enterprise Library was installed. By default, this is in C:\Program Files\Microsoft Enterprise Library January 2006\bin, but it could have been installed elsewhere so we better verify this with the user. The easiest way, of course, is to make this a recipe argument, just like with the application block's name and namespace:

<Argument Name="EnterpriseLibraryPath" Required="true">
  <ValueProvider Type="Evaluator" Expression="C:\Program Files\Microsoft Enterprise Library January 2006\bin" />
</Argument>

Notice that we're using the ExpressionEvaluatorValueProvider again, but this time without a complex expression. Used in this way, it just sets a default value for the argument that can be modified by the user later on. If we now want to add a binary reference to the Microsoft.Practices.EnterpriseLibrary.Common.dll assembly, for example, we can just add the following lines to the itemgroup containing the references:

<Reference Include="Microsoft.Practices.EnterpriseLibrary.Common, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null, processorArchitecture=MSIL">
  <SpecificVersion>False</SpecificVersion>
  <HintPath>$EnterpriseLibraryPath$\Microsoft.Practices.EnterpriseLibrary.Common.dll</HintPath>
</Reference>

Notice that the $EnterpriseLibraryPath$ parameter will automatically be replaced by the directory that the user chose in the wizard. So finally, the wizard's page definition will also need to include the new field:

<Field ValueName="EnterpriseLibraryPath" Label="Enterprise Library Path">
  <Editor Type="System.Windows.Forms.Design.FolderNameEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</Field>

This time, we're also providing an editor that allows the user to select the directory more easily using a folder browser window.

Adding A Post-Build Command

Now that we've referenced Enterprise Library, it's also a good time to make sure that the project output is always copied to the Enterprise Library directory. Otherwise, the application block is quite unuseable. The easiest way to do this is to add a post-build command to both projects that simply copy the project's assemblies to the EnterpriseLibraryPath the user has already chosen. Just like with the binary references, this is just a matter of modifying the MSBuild project file to include a new property group as such:

<PropertyGroup>
  <PostBuildEvent>$PostBuildCommand$</PostBuildEvent>
</PropertyGroup>

Creating the post-build command itself is a little trickier: it depends on the EnterpriseLibraryPath argument, and needs to build a more complex string based on that argument's value. The post-build command itself (as defined in Visual Studio) is simply:

COPY /Y $(TargetPath) "<Enterprise Library Path>"

Where we need to replace <Enterprise Library Path> with the value of the EnterpriseLibraryPath argument. So, how can we provide this value to the $PostBuildCommand$ argument? By building our own Value Provider, of course.

We start off by defining the argument itself:

<Argument Name="PostBuildCommand">
  <ValueProvider Type="JelleDruyts.EnterpriseLibraryGuidance.ValueProviders.PostBuildCommandValueProvider, JelleDruyts.EnterpriseLibraryGuidance"
                 EnterpriseLibraryPathArgument="EnterpriseLibraryPath">
    <MonitorArgument Name="EnterpriseLibraryPath" />
  </ValueProvider>
</Argument>

Notice that the value provider is now of our own type, namely the PostBuildCommandValueProvider we'll be building, and that it monitors the EnterpriseLibraryPath argument (since it depends on it). Furthermore, since the value provider needs access to the value of the EnterpriseLibraryPath argument, we also need to specify that argument's name, which is done through a custom EnterpriseLibraryPathArgument attribute on the ValueProvider node.

The code for the Value Provider isn't all that difficult, either. It's a class that inherits from the ValueProvider base class and that also implements the IAttributesConfigurable interface to support that custom EnterpriseLibraryPathArgument attribute. It can then override the OnArgumentChanged method that gets called when a monitored argument has changed, and re-generate the post-build command as such:

public override bool OnArgumentChanged(string changedArgumentName, object changedArgumentValue, object currentValue, out object newValue)
{
    // Get the name of the argument that defines the EnterpriseLibraryPath.
    string enterpriseLibraryPathArgumentName = m_Attributes[EnterpriseLibraryPathArgumentAttribute];
    
    // Get the dictionary that contains the recipe arguments.
    IDictionaryService dictionary = this.GetService<IDictionaryService>(true);

    // Get the current value of the EnterpriseLibraryPath argument.
    string enterpriseLibraryPath = (string)dictionary.GetValue(enterpriseLibraryPathArgumentName);

    // Create the post-build command.
    newValue = string.Format("COPY /Y \"$(TargetPath)\" \"{0}\"", enterpriseLibraryPath);
    return true;
}

Now we have an argument containing the post-build command that automatically contains the proper directory based on the Enterprise Library directory the user has chosen in the wizard dialog:

Where Are We

We've seen how to add binary references to the C# projects by modifying the MSBuild files and gathering user input through the wizard. We've also built a custom Value Provider that depends on the value of another argument, and used that as a post-build command. Next time, we'll see how we can generate some classes so that the application block can already be added to the Enterprise Library Configuration Tool.

Download the source code for the current state of the Guidance Package.

Thursday, June 29, 2006 8:19:25 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

GAS04: Adding Project References#

[This is episode 4 of the Guidance Automation Series]

Last time, we did a whole lot of work just to generate one very empty C# project. Just think of that as the healthy dish before the dessert: it's probably necessary but not really what you've been waiting for. This time, we'll really start using the power of Guidance Automation to generate a second project and add a project reference to it.

Adding Another Project

Now that we have seen how to generate the runtime project for the Enterprise Library Application Block, we'll just do the same thing to generate the design-time project. By convention, this has the same name as the runtime project, with ".Configuration.Design" added at the end. This is easily done in the solution's vstemplate file by adding a second project to the ProjectCollection node as such:

<ProjectTemplateLink ProjectName="$ApplicationBlockNamespace$.$ApplicationBlockName$.Configuration.Design">Projects\DesignTime\DesignTime.vstemplate</ProjectTemplateLink>

For this to work, we also need the DesignTime.vstemplate file in the proper location, but that's pretty easy to do: we can just copy the Runtime.vstemplate file and associated project files and make a few basic modifications to change the name. So with very little additional work, we have now generated two empty projects already.

Now of course, this design-time project will need information from the runtime project (e.g. the configuration types of the application block that are used by the runtime but need to be configured in the design-time project), so we need to add a project reference to it. Project references are actually stored in the referencing project's project file, but this isn't something we can generate out-of-the-box like before because it uses the GUID from the referenced project. This GUID is generated by Visual Studio at the time the solution is unfolded (remember the $guid1$ parameter) and we don't have access to that GUID outside the scope of that file. But Visual Studio knows how to add Project References, so we actually want to have the environment perform some action for us.

Actions

Actions are actually a big part of Guidance Automation, and they represent pieces of code that can run at the end of a recipe (and in other places too, but let's stick to the current problem). There are a lot of predefined actions in the Guidance Automation framework, but you can also define your own. To use an action, it can be registered in the Actions section in the recipe xml file, so that each one will be run in sequence at the end of the recipe. Actions have input and output arguments that can be configured, and it is important to note that these are strongly typed, which means that you're really passing objects from and actions and not just strings. The input arguments can either come from the recipe (e.g. a value the user has entered in the wizard), or they can be the output of another action.

Adding The Project Reference

To add a Project Reference, the predefined AddProjectReferenceAction seems to be just what we need here. It takes two input arguments: the referring project and the referenced project. Both are of the type EnvDTE.Project, meaning that they represent the actual project automation objects in Visual Studio. So we need a way to retrieve these projects, and that's where another action, aptly named GetProjectAction comes into play. This searches for a project based on its name and puts the project in an output argument so we can chain the two together as follows:

<Actions>
  <Action Name="GetRuntimeProject" Type="Microsoft.Practices.RecipeFramework.Library.Actions.GetProjectAction, Microsoft.Practices.RecipeFramework.Library">
    <Input Name="ProjectName" RecipeArgument="RuntimeProjectName" />
    <Output Name="Project" />
  </Action>
  <Action Name="GetDesignTimeProject" Type="Microsoft.Practices.RecipeFramework.Library.Actions.GetProjectAction, Microsoft.Practices.RecipeFramework.Library">
    <Input Name="ProjectName" RecipeArgument="DesignTimeProjectName" />
    <Output Name="Project" />
  </Action>
  <Action Name="AddProjectReference" Type="Microsoft.Practices.RecipeFramework.Library.Solution.Actions.AddProjectReferenceAction, Microsoft.Practices.RecipeFramework.Library">
    <Input Name="ReferringProject" ActionOutput="GetDesignTimeProject.Project" />
    <Input Name="ReferencedProject" ActionOutput="GetRuntimeProject.Project" />
  </Action>
</Actions>

Notice that the AddProjectReference action has inputs that come from the other two actions, and these in turn have inputs that come from the recipe. Now if you remember, the user didn't specifically provide the name of the projects to be generated, only the namespace and the name of the application block. We can use these arguments to derive the actual project names using the built-in ExpressionEvaluatorValueProvider.

Value Providers

A value provider is a class that provides values to arguments. Just like a Converter can convert and validate arguments, value providers can put values into arguments. Some of the built-in value providers can return the currently selected class in Visual Studio or a certain project by its name, but there's also a pretty powerful ExpressionEvaluatorValueProvider that can return values based on other arguments through expressions.

In this case, we simply want to build up the name of the runtime project by appending the two fields the user has entered:

<Argument Name="RuntimeProjectName">
  <ValueProvider Type="Evaluator" Expression="$(ApplicationBlockNamespace).$(ApplicationBlockName)">
    <MonitorArgument Name="ApplicationBlockNamespace" />
    <MonitorArgument Name="ApplicationBlockName" />
  </ValueProvider>
</Argument>

By also supplying the MonitorArgument values, the expression will automatically be re-evaluated if one of the monitored arguments has changed. We can now base the design-time project's name on this argument by appending a suffix to the project name as follows:

<Argument Name="DesignTimeProjectSuffix">
  <ValueProvider Type="Evaluator" Expression="Configuration.Design" />
</Argument>
<Argument Name="DesignTimeProjectName">
  <ValueProvider Type="Evaluator" Expression="$(RuntimeProjectName).$(DesignTimeProjectSuffix)">
    <MonitorArgument Name="RuntimeProjectName" />
    <MonitorArgument Name="DesignTimeProjectSuffix" />
  </ValueProvider>
</Argument>

Side note: it would also have been possible to use the expression "$(RuntimeProjectName).$(DesignTimeProjectSuffix).Configuration.Design" directly (without creating the DesignTimeProjectSuffix argument) but unfortunately there's a problem with the expression parser that makes the wizard very slow in this case.

Now that we have the names for both projects stored in arguments, we can also replace the project names in the vstemplate files that were still made up of the application block's name and namespace individually. E.g., we can replace ProjectName="$ApplicationBlockNamespace$.$ApplicationBlockName$" by ProjectName="$RuntimeProjectName$".

Where Are We

At this point, we've seen how to generate a second project with a project reference by using actions that are chained together. We've also seen how to use value providers that put values into arguments and how to use these values in actions. Next time, we'll see how to build our own value providers and have them interact with other arguments. Furthermore, we'll add binary references to some of the Enterprise Library assemblies and add post-build events to the projects. Stay tuned!

Download the source code for the current state of the Guidance Package.

Wednesday, June 28, 2006 8:06:58 PM (Romance Standard Time, UTC+01:00) #    Comments [3]  | 

 

GAS03: Generating A C# Project#

[This is episode 3 of the Guidance Automation Series]

Last time, we left off with an empty guidance package that we will now be using to build the Enterprise Library guidance package. To scope it down a bit, we'll only be supporting C# in this example - but of course the same principles apply to other languages. As a first step, we'll set up the package so that it will generate two projects: the runtime project that contains the logic for the application block, and the design-time project that contains the user interface logic for the configuration tool.

Templates And Recipes

Before diving in, you need to know that a guidance package is basically a combination of Visual Studio Templates and Guidance Automation Recipes.

  • Visual Studio Templates are the standard way to define types of solutions, projects and classes that the developer can create in Visual Studio (e.g. they show up in the Create New Project/Item dialog boxes). Visual Studio Templates are defined as .vstemplate files in the Templates directory of the project, in a subdirectory that defines the type of template (e.g. Solution or Projects).
  • Recipes are automated activities that follow a series of instructions to execute some action that a developer would otherwise need to do manually (e.g. creating a number of projects in Visual Studio and adding references to them). Recipes are defined in the main xml file (i.e. not the TypeAlias.xml file but the xml file named after your guidance package), which means configuring this part will require some xml tinkering - although fortunately there is pretty good IntelliSense support in Visual Studio.

The two are linked by adding a recipe reference to the vstemplate file that will make sure the recipe is executed at the time the solution is "unfolded" (which is just a fancy term to say "created" in Visual Studio).

The Solution Template

Armed with this knowledge, we can now create a basic vstemplate file in the Templates\Solutions directory that contains the template for the Application Block solution:

<VSTemplate Version="2.0" Type="ProjectGroup" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>Application Block</Name>
    <Description>Guidance Package that creates a new Enterprise Library Application Block.</Description>
    <ProjectType>CSharp</ProjectType>
    <SortOrder>91</SortOrder>
    <Icon>ApplicationBlock.ico</Icon>
    <CreateNewFolder>false</CreateNewFolder>
    <DefaultName>ApplicationBlock</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="$ApplicationBlockNamespace$.$ApplicationBlockName$">Projects\Runtime\Runtime.vstemplate</ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
  <WizardExtension>
    <Assembly>Microsoft.Practices.RecipeFramework.VisualStudio, Version=1.0.51206.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
    <FullClassName>Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate</FullClassName>
  </WizardExtension>
  <WizardData>
    <Template xmlns="http://schemas.microsoft.com/pag/gax-template" SchemaVersion="1.0" Recipe="CreateApplicationBlock">
    </Template>
  </WizardData>
</VSTemplate>

There are a few interesting things to note here:

  • The TemplateData section covers the metadata for the solution: its icon, name and description as they appear in the New Project dialog box, the project type, and some other properties.
  • The TemplateContent section defines the structure of the solution; in this case we define a project collection (we'll be adding another project later on) that contains one project template for the runtime part of the application block. When the solution is created, the template defined in the Runtime.vstemplate file will also be "unfolded".
  • The project's name is defined as "$ApplicationBlockNamespace$.$ApplicationBlockName$"; this is the first appearance of parameters (enclosed in $-signs). These values will be replaced at runtime by arguments from the Guidance Recipe, which we will define later on, to form the project name in Visual Studio.
  • The Template node in the WizardData section establishes the link between the vstemplate and the corresponding "CreateApplicationBlock" recipe that needs to be defined in the main xml file.

Guidance Automation Tip #2: All Visual Studio template files (vstemplates and their contents such as project files, code files and images) must have their "Build Action" set to "Content" and "Copy to Output Directory" to "Copy Always". The Guidance Automation Toolkit adds an item in the Templates directory's context menu that will do this for you.

The Project Template

At this point, we have a template for the solution that contains a link to a project template and to a recipe. The Runtime.vstemplate project template looks like this:

<VSTemplate Version="2.0.0" Type="Project" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>Application Block Runtime Project</Name>
    <Description>A project template for the runtime part of an Application Block.</Description>
    <Icon Package="{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}" ID="4547" />
    <ProjectType>CSharp</ProjectType>
    <SortOrder>20</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>Runtime</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
  </TemplateData>
  <TemplateContent>
    <Project File="Runtime.csproj" TargetFileName="$ApplicationBlockNamespace$.$ApplicationBlockName$.csproj" ReplaceParameters="true">
      <ProjectItem ReplaceParameters="true">Properties\AssemblyInfo.cs</ProjectItem>
    </Project>
  </TemplateContent>
  <WizardExtension>
    <Assembly>Microsoft.Practices.RecipeFramework.VisualStudio, Version=1.0.51206.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</Assembly>
    <FullClassName>Microsoft.Practices.RecipeFramework.VisualStudio.Templates.UnfoldTemplate</FullClassName>
  </WizardExtension>
  <WizardData>
    <Template xmlns="http://schemas.microsoft.com/pag/gax-template" SchemaVersion="1.0">
    </Template>
  </WizardData>
</VSTemplate>

Some highlights from this file:

  • The icon from the package {FAE04EC1-301F-11d3-BF4B-00C04F79EFBC} is the ID of an icon in the C# Project dll, typically located at C:\Program Files\Microsoft Visual Studio 8\VC#\VCSPackages\csproj.dll. This dll can be opened in Visual Studio so you can pick another icon from this dll if you want, but you can also use a custom icon like we did in the solution's template above.
  • The TemplateContent section now defines a project that is created from the Runtime.csproj file. This is a regular C# project file that can again contain some parameters that will be replaced at runtime (if the ReplaceParameters attribute is set to true, anyway).
  • Within the project, we also include a Project Item (typically a file within the project), which will in this case be a boilerplate AssemblyInfo.cs file that the project will already contain.
  • There is no link to a recipe in the Template node here, since this project will only be unfolded by the solution template defined above - which already contains a reference to a recipe.

The Project File

This is the content of the Runtime.csproj file:

<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>8.0.50727</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>$guid1$</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>$safeprojectname$</RootNamespace>
    <AssemblyName>$safeprojectname$</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <DocumentationFile>bin\Debug\$safeprojectname$.xml</DocumentationFile>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <DocumentationFile>bin\Release\$safeprojectname$.xml</DocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Data" />
    <Reference Include="System.Xml" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Properties\AssemblyInfo.cs" />
  </ItemGroup>
  <Import Project="$(MSBuildBinPath)\Microsoft.CSHARP.Targets" />
</Project>

Again, some interesting points to look at here:

  • There is no magic here, this is just an MSBuild project file for C# that you can tweak as much as you want.
  • Not only can we use the $ApplicationBlockName$ and other parameters we defined in the recipe, but we also have some standard parameters that can be used (see Template Parameters on MSDN for more information). The $guid1$ parameter will be replaced by a unique GUID (you can define up to 10 different GUIDs like this), the $safeprojectname$ will be replaced by the name provided by the user in the New Project dialog box, with all unsafe characters and spaces removed.
  • Both for the Debug and Release configurations, I've set TreatWarningsAsErrors to true (to enforce the best practice that warnings should be taken seriously) and I've enabled the output of an XML documentation file. The combination of these two settings also enforces that all public members must have XML comments, another best practice I always try to enforce.
  • The ItemGroup section defines that the AssemblyInfo.cs file we included in the project template is part of the project and needs to be compiled.

The Recipe

Now that we have all the templates in place, we still need to define the recipe that will enable the user to automate the creation of the solution and its projects. In its simplest form, you can think of a recipe as a wizard that collects user data - but as we will see later on in this series, it can do a lot more. We've currently defined two custom parameters in the template files: $ApplicationBlockName$ and $ApplicationBlockNamespace$. This means we need to collect this information from the user when the solution is being created. The following recipe definition does just that:

<?xml version="1.0" encoding="utf-8" ?>
<GuidancePackage xmlns="http://schemas.microsoft.com/pag/gax-core"
    Name="JelleDruyts.EnterpriseLibraryGuidance" 
    Caption="Enterprise Library Guidance"
    Description="Provides guidance around the creation of Enterprise Library Application Blocks"
    Guid="2cac5b9c-a04f-4a49-8a56-3ee5d63bd83f" 
    SchemaVersion="1.0">
  <Recipes>
    <Recipe Name="CreateApplicationBlock">
      <Caption>Create a new Enterprise Library Application Block</Caption>
      <Arguments>
        <Argument Name="ApplicationBlockName" Required="true">
          <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.CodeIdentifierStringConverter, Microsoft.Practices.RecipeFramework.Library" />
        </Argument>
        <Argument Name="ApplicationBlockNamespace" Required="true">
          <Converter Type="Microsoft.Practices.RecipeFramework.Library.Converters.NamespaceStringConverter, Microsoft.Practices.RecipeFramework.Library" />
        </Argument>
      </Arguments>
      <GatheringServiceData>
        <Wizard xmlns="http://schemas.microsoft.com/pag/gax-wizards" SchemaVersion="1.0">
          <Pages>
            <Page>
              <Title>Application Block Information</Title>
              <LinkTitle>Application Block</LinkTitle>
              <Help>
                Enter the Application Block name and namespace.
              </Help>
              <Fields>
                <Field ValueName="ApplicationBlockName" Label="Application Block Name" InvalidValueMessage="Must be a valid .NET identifier (e.g. it shouldn't contain spaces or special characters)." />
                <Field ValueName="ApplicationBlockNamespace" Label="Application Block Namespace" InvalidValueMessage="Must be a valid .NET namespace identifier (e.g. it shouldn't contain spaces or special characters)." />
              </Fields>
            </Page>
          </Pages>
        </Wizard>
      </GatheringServiceData>
    </Recipe>
  </Recipes>
</GuidancePackage>

Some remarks on this file:

  • Recall that the "CreateApplicationBlock" recipe that is defined here is referenced by the solution's template, so this is the recipe that will be used to gather the data from the user.
  • The Arguments section defines all the recipe arguments that are defined, and can be replaced in the template using parameters. In this case, we have two arguments, ApplicationBlockName and ApplicationBlockNamespace, which are both required. They also have converters associated with them that can validate the arguments (e.g. to make sure they are valid .NET identifiers or namespaces). If the argument is invalid, it will immediately become visible to the user. Likewise, a required argument will have a light yellow background in the wizard, so it's immediately clear which arguments need to be filled in and which can be skipped.
  • The GatheringServiceData section contains a wizard with one page and two fields, one for each argument we want to collect.

Running The Guidance Package

Now that we have all the parts covered, we can finally register and test our package. It will show up as the "Application Block" project type in Visual Studio (with the proper icon and description):

When we create the solution, a wizard will pop up that allows us to enter the information required to generate the projects. Notice that the fields have a light yellow background and that we get an error icon and a tooltip if some argument is invalid:

When we correct the information and press Finish, our first skeleton project for the Enterprise Library Application Block has been created:

Note that the project was correctly named using the combination of the namespace (JelleDruyts.EnterpriseLibrary) and name (ServiceAgents) that was entered in the wizard.

Where Are We

At this point, we've seen how to define Visual Studio Templates that accept parameters and create solution files, project files and project items. We can already impose some guidance and best practices by tweaking the projects (e.g. to treat warnings as errors). We've also seen how to define a recipe that gathers information from the user to pass along to the templates.

You might say, "well, that's an awful lot of work to create a blank project", and you would be absolutely right. But of course, this is just the beginning. And now that we have the basics covered, the fun can really start! Stay tuned for the next episode, in which we'll take this guidance package a step further.

Download the source code for the current state of the Guidance Package.

Tuesday, June 27, 2006 9:35:19 PM (Romance Standard Time, UTC+01:00) #    Comments [8]  | 

 

GAS02: Creating A Guidance Package#

[This is episode 2 of the Guidance Automation Series]

In this first real part of the Guidance Automation Series, we'll start off slowly and easily... If you want to follow along, be sure to install both the Guidance Automation Extensions (GAX) and the Guidance Automation Toolkit (GAT).

Once you've installed GAT, you'll notice a new project type in Visual Studio called "Guidance Packages". This will contain all the packages that were installed on this machine. At this time, a first package is even already registered: the "Guidance Package" package.

See, creating a guidance package from scratch yourself would be quite a hassle, so this über-package will generate a sample package for you that you can use to immediately start building your own. People that can handle more recursive thinking than me often call this a Software Factory Factory, because it creates something that will in turn create something else.

Anyway, if you create a new project using this "Guidance Package" package, you'll get quite a lot of code that shows off a big number of the available features, as well as two additional projects that together form the setup project for your package. You can now build this entire solution, right-click the guidance package project and select "Register Guidance Package" from the context menu.

When the registration process has finished, you're ready: your first guidance package has been built and deployed on your local machine. You can test it by opening another instance of Visual Studio, where you should now notice your own package in the "Guidance Packages" project type.

Using the MSI that was built by the setup project, you can now easily deploy this Guidance Package to any developer pc that has the Guidance Automation Extensions installed.

Guidance Automation Tip #1: Always keep your guidance package open in one dedicated instance of Visual Studio and test the package in new instances every time.

Notice that registering the package took quite some time, and unfortunately this is life in the Guidance Automation world: everytime you want to verify or test your package, you'll have to register it and start another Visual Studio to test it in. So my development cycle typically looks as follows: make modifications to the package, build the package, register the package, open another Visual Studio, test the package, make modifications to the package, ... In other words: think twice before you think your modification is ok :-)

The good news is that with this new release, a new "Quick Register" option has been added to the context menu, as you can see in the screenshot above. You can use this to drastically speed up the development process if you don't have any new recipes or templates that need to be registered or if you didn't change any HostData sections that define the integration with Visual Studio. If you did any of these things, you will need to re-register the entire package, though.

Now that we have the basics covered, let's remove all the pre-generated parts from the package that aren't needed so that we're left with a clean and empty Guidance Package. This basically means deleting all the files in the package except the two xml files in the project root: these form the package definition and are used by GAX to register it. This empty solution will be the basis on which we'll build the Enterprise Library Guidance Package in the next installment of this series.

Download the source code for the current state of the Guidance Package.

Monday, June 26, 2006 9:09:55 PM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

GAS01: Introduction To The Guidance Automation June 2006 CTP#

[This is episode 1 of the Guidance Automation Series]

Great news: the June 2006 CTP of Guidance Automation (GAX and GAT) has just been released! Although it's still labeled a CTP (because it's unsupported), I personally think of this as the 1.1 release of a great tool I've been using extensively the last couple of weeks.

If you've never heard of the Guidance Automation Extensions (GAX) and the Guidance Automation Toolkit (GAT), this is the elevator pitch straight from the Guidance Automation community site:

With the Guidance Automation Toolkit, you can make reusable code and pattern assets directly available in Visual Studio 2005. It is designed to simplify integrating reusable code into applications. This lets architects automate processes, and guide their developers through complex procedures that would otherwise need to be thoroughly documented.

So in other words: the Guidance Automation Toolkit provides a means for a solution architect or lead developer to deploy consumable guidance to developers. If you've ever published a 40-page document full of guidelines, do's and don'ts on how to structure solutions and projects in your company and what the namespaces and class names should be, if the developers need to be able to start new projects quickly, if you have a need for code generation, or all of the above - this toolkit is for you. Microsoft is itself using this technology to publish their Software Factories, like the Web Service Software Factory, so that says something about the quality and future of this fine piece of software.

Just to get some basic vocabulary right: the Guidance Automation Extensions (GAX) is the runtime part that needs to be installed on the machines of the developers that want to use the Guidance Packages. The Guidance Automation Toolkit (GAT) is the authoring part that helps you create the Guidance Packages and the setup projects to install them.

New in this release of GAX are the Guidance Navigator window, which provides a separate window listing all the recipes in the currently loaded guidance package (thereby greatly enhancing their discoverability) and the fact that the state of the guidance package (which actions can be performed where in the solution, for example) isn't persisted in the per-developer .suo file anymore but in a separate .gpstate file so team development becomes possible. A very handy new feature of GAT is the Quick Register action that can drastically speed up the development process.

In a series of posts I'll commonly call the Guidance Automation Series (or GAS for short, if you will), I'll be showing you how to build a Guidance Package with this new release for a sample scenario that's related to that other big project from the Microsoft patterns & practices group: Enterprise Library 2.0. I'll be implementing a Guidance Package that allows you to quickly start building your own application blocks that plug in nicely with the rest of Enterprise Library. The point is not to provide a full-blown Guidance Package with all the bells and whistles required to start writing these application blocks, the idea is to show you how to create the Guidance Package - so you can go the extra mile of adding all the bells and whistles yourself :-) If you need more details on how to build the application blocks themselves, I can highly recommend the "Speed Development With Custom Application Blocks For Enterprise Library" article by Mark Seemann that was just published on MSDN.

So, if you're into this kind of stuff, keep reading this blog :-)

Monday, June 26, 2006 8:50:14 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

The Visual Studio 2005 Toolbox, UserControls and Solution Folders#

There's a pretty inconvenient bug in the Windows Forms designer in Visual Studio 2005, which apparently is a known issue but not scheduled to be fixed anytime soon. Here's the scoop and a (slightly sucky) workaround...

If you have a User Control in your project, it will automatically get added to the Toolbox window so you can drag it onto your designer surface. However, it doesn't show up in the Toolbox if the project is inside a Solution Folder (which is a virtual folder in your solution). It would be really nice if it were also possible to drag the user control from the Solution Explorer onto the designer surface, but unfortunately that's not supported.

So a possible workaround is to take the simplest type of control available (I tend to go for a plain old Panel) and add that to the form in stead of the user control you really want to add. Then switch to the designer-generated code (e.g. MyForm.Designer.cs) and change both the declaration of the panel (e.g. "private System.Windows.Forms.Panel panel1;") and the instantiation (e.g. "this.panel1 = new System.Windows.Forms.Panel();" inside the "Windows Form Designer generated code" region) to your user control type (e.g. MyNamespace.MyUserControl).

It sucks, but it works.

Blog | Programming | .NET | Quirks | VS.NET | WinForms
Friday, June 23, 2006 5:20:59 PM (Romance Standard Time, UTC+01:00) #    Comments [5]  | 

 

A fix for "Could not access network location" when installing a Visual Studio Addin#

Just a quick hint: if you're installing addins for Visual Studio and the installer quits with a "Could not access network location "\\SomePath\Visual Studio\Addins", this probably means your "My Documents" folder is stored on a network drive, causing the installer to fail (for some reason I haven't yet discovered).

I found a pretty quick workaround, which is temporarily changing the location of the "My Documents" folder to a path on a local drive. You can do this by changing the registry key (all usual disclaimers apply, I didn't touch your computer so I didn't break it) "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurentVersion\Explorer\User Shell Folders\Personal". Note that the installer won't actually use this path to store anything, so it's safe to use any path you deem fit. If the addin installed properly after this hack, don't forget to change the registry key back to its original value and you should be fine.

I haven't tried it for other similar issues, but it might even work with any other type of software that breaks with the same error message.

Blog | Programming | .NET | VS.NET
Wednesday, June 21, 2006 5:59:39 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Creating a setup package with multiple optional custom actions#

I've recently needed to create an installer with custom actions that only need to be run when the user selects certain options in the installation wizard. It's pretty easy to do once you know the pitfalls, but it took me some time to figure out so I figured I might as well post it for future reference...

One option is to create a separate assembly for each custom action that can be selected, and then setting its Condition property so that it is only executed if the user chose the corresponding option.

However, this could result in quite a lot of extra assemblies (currently three in my case, but it might become more), which I try to avoid to make it easier to deploy and use these custom actions. So I wanted one assembly containing all the installer classes that could need to be run, and then decide inside that assembly which ones to execute based on the user's choice. This is what worked for me:

  • Pass in the user's choices (e.g. the property values of the checkboxes) through the custom action's CustomActionData property, e.g. "/OPTION1=[OPTION1] /OPTION2=[OPTION2]" in case there are two checkboxes with OPTION1 and OPTION2 properties. Also pass in all other data that needs to be available for the individual installers (note that these may conflict, in which case you'll have to create different assemblies anyway).
  • Mark each individual installer with the [RunInstaller(false)] attribute. Otherwise, the installer will be executed no matter what. Side note: my first attempt at solving this problem was to check inside each installer's Install method if it needed to be executed based on the context parameters, but this caused all sorts of problems with the savedState dictionary.
  • Create one main installer class with [RunInstaller(true)]. This is the one we will add the decision-making logic in, and which will keep the master savedState dictionary.
  • In this main installer, override the Install method and, before calling base.Install, add the individual installers to its Installers property depending on the parameters that were passed in. Also, remember which installers were added (which options were selected) by putting them in the stateSaver object. Example:
    if (this.Context.Parameters["OPTION1"] == "1")
    {
      this.Installers.Add(new Option1Installer());
      stateSaver["OPTION1"] = "1";
    }
    It's important to only call base.Install after this, because the base implementation will then install all the child installers that were just added.
  • Override the Commit and Rollback methods, and before calling the base class implementation, add the installers again depending on the values you put in the savedState object. This is necessary because these methods will be called on new instances of your installer class, so you need to re-add them to the Installers property. Example:
    if (savedState["OPTION1"] == "1")
    {
      this.Installers.Add(new Option1Installer());
    }
  • Do the same thing for the Uninstall method, this way you are sure that you're only uninstalling the parts that were actually installed. This is important, because when uninstalling, the parameters that are passed in do not remember the user's initial choices (i.e. OPTION1 might be set to "1" when uninstalling, even if the user didn't select that choice at installation time).

This is what worked for me, if there are simpler ways of achieving the same thing, don't hesitate to make suggestions :-)

Blog | Programming | .NET | VS.NET
Wednesday, June 21, 2006 5:55:35 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Visual Studio 2005 Web Application Projects Released!#

I don't know about you, but I've never been very fond of the new Web Project System in ASP.NET 2.0. I'm sure it has its advantages for some types of users but I've definitely had more than my share of problems with it. Specifically the way that it sometimes (and randomly) starts checking in binaries in Source Control has shortened my life with at least 4 months. Furthermore, having each page compile into its own assembly (by default anyway) and therefore not having a nice place to put assembly-level attributes or define strong naming that work on the entire website has surprised me (in a negative way, that is). What I don't get is that Microsoft had abandoned a familiar and working concept (the Web Application model used in the previous versions of Visual Studio) and replaced it entirely without supporting the old way of working. To me this has always been a major breaking change, and a cause of much frustration.

Well, the pain has now officially ended: the Visual Studio 2005 Web Application Projects has been released, meaning that the old model is back with a vengeance. With a free download from Microsoft, you can get your familiar working environment back in Visual Studio 2005, but with lots and lots of improvements over the previous versions of course (not even mentioning the fact that it's now ASP.NET 2.0 with all its goodness around it).

Some highlights:

  • A project file (which I don't consider a feature, more a bugfix over the previous model)! This fixes the problems with the references getting checked in, and lots of other things.
  • Because of the project file, you also have pre- and post-build events again (the new Web Project System doesn't support them).
  • You can create working Web Setup projects again, containing the content and primary output of the project. With the new Web Project System, you have to download and install a separate update (the Visual Studio 2005 Web Deployment Projects).
  • It builds one assembly by default. This fixes the problem with the assembly-level attributes and strong naming.
  • The Global.asax file isn't ugly inline script anymore but a proper class in a code-behind file. The "Application_Start" and similar methods are still "magic" though, discovered through reflection I assume, in stead of properly overriding them from the base class or something.
  • No more Frontpage Extensions required. Be gone, _vti_*!
  • Built-in Development Web Server. Be gone, IIS! Well, not for enterprise scenarios of course, but it's still a very nice feature :-)
  • MSBuild support.
  • Better web publishing options.
  • ...

I've immediately installed and tried it and it works great! One specific detail that I think is a very nice feature (just to show that it's not a quick fix of the old model to run on Visual Studio 2005 but a real full-blown upgrade) is the "Create Virtual Directory" button in the project settings, which allows you to setup a virtual directory in IIS without having to go to the IIS console:

Great!

Anyway, this add-on has made me very happy with ASP.NET 2.0 again. It's the little things that make the difference, but the big things in this case made my day :-)

Blog | Programming | .NET | ASP.NET | VS.NET
Thursday, May 11, 2006 8:30:11 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Xml Documenter Macro#

Although I've argued before that Typed DataSets aren't evil, there's one thing that bothers me a lot when dealing with them in a library project: the code that gets generated doesn't contain any XML comments. In itself, that isn't so bad (after all, which useful comments could be generated from an XSD schema?), but if the project is configured to emit an XML documentation file, you get a gazillion warnings for each member that lacks a comment. I like to keep my build warning-free to avoid "known harmless warnings" from hiding "new and important warnings" (if I can first get my stuff to build without errors, at least). You could go about and put comments on the generated file manually, but any change on the dataset's schema file will trigger the code to be regenerated so you've lost all your hard work. Hence I created the following quick and dirty solution: a Visual Studio macro that runs over all members and puts a placeholder comment (containing just the member's name) on top. Anytime the code gets regenerated, just run the macro and you're set. I'm sure this should be able to run as a post-build step or something too, but if you're lazy like me (and you're a programmer too, so you're lazy by definition), you'll already be happy you got this done.

Oh, and if you're really lazy, you can use this macro to document all your members automatically - although I'd add some random word-generator to make it look just a little more convincing (to fool the QA-guys at least).

So here's the source (with special thanks to the MSDN article on XML Comment Macros):

Imports EnvDTE
Imports System.Diagnostics
Imports System
Imports System.Xml
Imports System.IO
Imports System.Collections
Imports System.Text
Imports System.Text.RegularExpressions

Public Module XmlDocumenter

    Public Sub InsertGenericSummaries()

        SetCommentsRecursively(DTE.ActiveDocument.ProjectItem.FileCodeModel.CodeElements)

    End Sub

    Private Sub SetCommentsRecursively(ByVal elements As CodeElements)

        For Each element As CodeElement In elements
            ' Get a comment block.
            Dim doc As XmlDocument = GetCommentXml(element)
            Dim summaryTag As XmlElement = doc.SelectSingleNode("/doc/summary")
            If summaryTag Is Nothing Then
                ' Create <summary> tag
                summaryTag = doc.CreateElement("summary")
                summaryTag.InnerXml = element.Name
                doc.FirstChild.PrependChild(summaryTag)
            End If
            SetCommentXml(element, doc)

            ' Recurse over members.
            Try
                SetCommentsRecursively(element.Members)
            Catch ex As Exception
                ' Ignore exceptions (quick and dirty hack to skip elements that have no Members property)
            End Try
        Next

    End Sub

    ' Code borrowed from http://msdn.microsoft.com/msdnmag/issues/05/07/XMLComments/default.aspx
    Private Sub SetCommentXml(ByVal element As CodeElement, ByVal doc As XmlDocument)
        Dim sb As StringBuilder = New StringBuilder
        Dim sw As StringWriter = New StringWriter(sb)
        Dim xw As XmlTextWriter = New XmlTextWriter(sw)

        Try
            xw.Indentation = 1
            xw.IndentChar = Char.Parse(vbTab)
            xw.Formatting = Formatting.Indented

            Dim rootNode As XmlNode
            If element.Language = CodeModelLanguageConstants.vsCMLanguageVB Then
                rootNode = doc.FirstChild
            Else ' CSharp
                rootNode = doc
            End If

            rootNode.WriteContentTo(xw)
            Try
                element.DocComment = sb.ToString()
            Catch ex As Exception
                ' Ignore
            End Try
        Finally
            xw.Close()
            sw.Close()
        End Try
    End Sub

    ' Code borrowed from http://msdn.microsoft.com/msdnmag/issues/05/07/XMLComments/default.aspx
    Private Function GetCommentXml(ByVal element As CodeElement) As XmlDocument

        Dim doc As XmlDocument = New XmlDocument

        Dim xmlStr As String = element.DocComment
        If Not xmlStr Is Nothing AndAlso xmlStr.Trim() <> String.Empty Then
            If xmlStr.StartsWith("<doc>") = False Then
                ' Ensure single root node
                xmlStr = "<doc>" & xmlStr + "</doc>"
            End If
            xmlStr = Regex.Replace(xmlStr, "^(\s*<doc>\s*){2,}", "<doc>")
            xmlStr = Regex.Replace(xmlStr, "(\s*<\/doc>\s*){2,}$", "</doc>")
            doc.LoadXml(xmlStr)
        Else
            Dim rootTag = doc.CreateElement("doc")
            doc.AppendChild(rootTag)
        End If

        Return doc
    End Function

End Module

Easy, effective, and ugly. Use at your own risk, it might eat your lunch sandwich.

Blog | Programming | .NET | VS.NET | Samples
Monday, January 16, 2006 8:49:00 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Article: Customizing generated Web Service proxies in Visual Studio 2005#

I'm proud and honored to see that my first article has been published on the Microsoft Belux website:

Customizing generated Web Service proxies in Visual Studio 2005

As the title implies, it talks about how you can customize the Web Service proxies that are generated by the "Add Web Reference" dialog box in Visual Studio 2005 or the WSDL.exe tool through a new mechanism introduced in .NET 2.0 called "Schema Importer Extensions".

Don't hesitate to let me know if you liked or disliked the article or the proposed solution!

Articles | Blog | Programming | .NET | VS.NET | Whidbey
Tuesday, November 29, 2005 9:47:26 PM (Romance Standard Time, UTC+01:00) #    Comments [8]  | 

 

Setting a default value for a color property#

Here's a tip on how to set the default value of a color property in the Visual Studio designer.

If you have a component that has a BackColor property, for example, and you want to indicate to the Property Grid that its default color is defined as the RGB values 255/255/192 (light yellow) then you might have some trouble. You can initialize a color at runtime from these RGB values by calling Color.FromArgb(255, 255, 192), but since there's no matching constant, you can't pass this into the DefaultValueAttribute constructor:

[DefaultValue(Color.FromArgb(255, 255, 192))] // Won't compile, needs a constant value
public Color BackColor { get { ... } set { ... } }

You can try using the overloaded constructor to pass in the type of the object and an initialization string that can be parsed by a TypeConverter (just as when you type it directly into the property grid), but for some reason I've yet to discover that won't work if you use the RGB values:

[DefaultValue(typeof(Color), "255; 255; 192")] // Won't work

The trick is to use the hexadecimal value of the RGB values as such:

[DefaultValue(typeof(Color), "0xFFFFC0")] // Works, 0xFF=255, 0xFF=255, 0xC0=192

Note that you can also define system or well-known colors like this:

[DefaultValue(typeof(Color), "Red")] // Works, Red=0xFF0000

Also, check out my post on specifying default values for enums.

Blog | Programming | .NET | Quirks | VS.NET
Wednesday, November 23, 2005 9:02:35 AM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

Downloading Visual Studio 2005 RTM#

The RTM version of Visual Studio 2005 has finally arrived! Downloading the goodies is still quite a pain though, I couldn't get any download started through MSDN Subscriptions yesterday, my colleague had more luck after trying a few times this afternoon but then he had a little hickup as well:

Fortunately, File Transfer Manager can resume :-)

Other than that, it seems that the Visual Studio Team Suite Trial Edition that was available yesterday has already been removed from the Subscriptions download page, as well as the Beta 3 Refresh of Team Foundation Server (also spotted by Sam Gentile). What's up with that?

Blog | Programming | .NET | VS.NET | Whidbey
Friday, October 28, 2005 3:50:25 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Are you ready for the launch?#

In just two weeks, Microsoft Belux is organizing what I personally think is the biggest launch event in the last couple of years: on November 10, SQL Server 2005, Visual Studio 2005 and BizTalk 2006 will finally be released to manufacturing!

Developers and IT professionals are invited to celebrate the launch of the new versions of Visual Studio, SQL Server and BizTalk Server together with us. The event will start with a keynote session by S. Somasegar, Corporate Vice President at Microsoft Corporation.

Further some of the best speakers and subject matter experts worldwide will entertain you. Make sure you bring along your most burning SQL Server 2005 or Visual Studio 2005 questions.

Later in the evening we offer a walking dinner and a party to remember...

You can find more information and subscribe for the event on the Microsoft Belux Launch site.

I've been asked to hang out at the Ask-The-Experts area as a Visual Studio Expert, so if you're there: come say hi and ask me some tough questions :-)

See you there!

Blog | General | Programming | .NET | VS.NET | Whidbey
Wednesday, October 26, 2005 6:36:12 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Copy To Output Directory#

Here's a tiny new feature in Visual Studio 2005 that caught my attention:

Pretty nice, now you don't need a post-build step with macros anymore to get some files into the output directory.

It doesn't seem to work all-the-way though: when running a Test Project, this setting won't copy the file into the running test's current directory. Other than that, it's still nice to see the "little things" that so often make the difference...

Blog | Programming | .NET | VS.NET | Whidbey
Thursday, October 20, 2005 10:22:08 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Two Cool Tools#

I've said before that I'm not a "linking out" kinda guy, but these two tools are just too cool not to mention.

CR_Documentor

CR_Documentor is a free addin for Visual Studio (works on Visual Studio 2003 and 2005!) that shows a live preview of how the XML documentation under your cursor would look like in a help file.

I'm a real documentation freak, so this is pure heaven for me!

PowerTrace

PowerTrace is a free library that contains the TerminalTraceListener class, which "opens a port and allows you to connect to your app through Telnet so you can watch your traces in the lab or even across the globe" (quoting the November 2005 edition of MSDN Magazine's Bugslayer column).

I think that's a pretty clever concept: you're not wasting any disk space with trace files, in stead you can connect to a running app when there's trouble. You don't receive a history of the last x minutes or trace lines when you login though (which you will probably need to properly diagnose the problem) but it's still a great idea to add this functionality to production systems.

Blog | Programming | .NET | VS.NET | Whidbey
Tuesday, October 18, 2005 5:36:41 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

ASP.NET 2.0 References#

I've been using the Release Candidate of Visual Studio 2005 lately, and I'm very pleased with the overall look and feel of the application. There are still some issues left to work out (I get the occasional crash and burn), so I hope they'll all be gone by the time they RTM in the week of November 7th...

One of the things I really wished they would have fixed, though, is the way references are handled in ASP.NET 2.0 websites or WebService projects. This is what I had to say about references in June 2004, and it still holds true:

Although certainly not always possible, the ultimate solution to problems with references has always been using Project References. So that's what I'm doing now, but in ASP.NET they tend to switch from project references to fixed binary references that don't get updated anymore. And you wonder why you get runtime errors... So you need to check those references regularly and reset them to project references. Joy! What's even funnier is that an ASP.NET site doesn't have a project file anymore, which would be fine if there was nothing to remember for an ASP.NET project. But what about these references? I'm sorry to have noticed that, but they're stored in the solution file. Bad, bad, bad... If you have multiple solutions with the same ASP.NET project in them, it would depend on the solution file which kind of references the project would use. If you really need project-settings, revive the project file guys and recall the "no project file needed"-hype. What's one file extra going to do anyway?

Apart from the project references randomly becoming binary references (I've seen it happen again in the RC!) I still think it's an even bigger problem that project properties are put into the solution file. If you want proof, this is a part of a solution file that contains a WebService project:

Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = http://localhost/TestWebService/,
"http://localhost/TestWebService", "{F7389224-931A-4652-A0E2-3A932D1B40C6}"
 ProjectSection(WebsiteProperties) = preProject
  ProjectReferences = "{BC53180F-394F-4F31-A61E-BFBC4DBF9B32}|BusinessLayer.dll;"
  Debug.AspNetCompiler.VirtualPath = "/TestWebService"
  Debug.AspNetCompiler.PhysicalPath = "..\..\..\..\POC\TestWebService\"
  Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\TestWebService\"
  Debug.AspNetCompiler.Updateable = "true"
  Debug.AspNetCompiler.ForceOverwrite = "true"
  Debug.AspNetCompiler.FixedNames = "false"
  Debug.AspNetCompiler.Debug = "True"
  Release.AspNetCompiler.VirtualPath = "/TestWebService"
  Release.AspNetCompiler.PhysicalPath = "..\..\..\..\POC\TestWebService\"
  Release.AspNetCompiler.TargetPath = "PrecompiledWeb\TestWebService\"
  Release.AspNetCompiler.Updateable = "true"
  Release.AspNetCompiler.ForceOverwrite = "true"
  Release.AspNetCompiler.FixedNames = "false"
  Release.AspNetCompiler.Debug = "False"
  SlnRelativePath = "..\..\..\..\POC\TestWebService\"
 EndProjectSection
EndProject

The rest of the settings are also project-specific (hence the ProjectSection) so I think it's all unacceptable. But I guess it's too late to change any of this...?

Update: I did some thinking and some of this might actually make sense... As the line in the solution file above actually states, it's a Project Reference. Since project references refer to other projects within the same solution file by definition, this is actually quite logical. I tried to do the same thing with a non-project reference (i.e. a binary reference) and it's not stored in the solution file. But before you start cheering, be aware that the reference isn't stored anywhere at all. The assembly is just copied over to the website and you'll have to update it manually if the reference changed. Not too brilliant either... The fact that project references tend to switch to binary references might have something to do with adding the website to a new solution which doesn't contain the referenced project, but I'm not so sure the reason is that simple.

Blog | Programming | .NET | ASP.NET | VS.NET | Whidbey
Thursday, October 13, 2005 8:23:50 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Deleting source-controlled items#

Now here's a dialog box in the Visual Studio 2005 RC I'm very happy with:

Finally, no more orphaned files in your Source Control Repository when you delete files from your project locally!

Blog | Programming | .NET | VS.NET | Whidbey
Thursday, October 13, 2005 9:51:34 AM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

WeFly247 Screenshots#

Now that the word on WeFly247 is gradually getting out, here is some more teasing with some screenshots of the different applications in a near-finished state. Click on an image to get an enlarged version.

The Login screen (reused by different Windows applications)

The Passenger website (ASP.NET 2.0 application)

Homepage (not logged in):

City Information:

Homepage (logged in):

Preferences:

Entertainment:

Meal Service:

Seat Service:

Duty-Free Shop:

Customs & Immigrations InfoPath Form:

The Pilot Application (ClickOnce Windows Forms application)

Startup Screen:

Pre-Flight Check:

In-Flight Monitoring:

Problems:

Problems (Professional Look):

The Problem Report Word Document (VSTO Smart Document)

Problem Report:

Notice the Flight Number Smart Tag (which fills in the flight details) and the custom actions pane showing the open problems.

Problem Report Research:

This shows the Research webservice that can be used to diagnose problems in real time from within the Smart Document.

Blog | Programming | .NET | VS.NET | WeFly247 | Whidbey
Sunday, April 10, 2005 12:51:09 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Visual Studio .NET 2005 Beta Experience#

Tom has already been teasing about the Visual Studio .NET 2005 Beta Experience, and since it's getting more and more traction, I can finally say we're shipping WeFly247 in the EMEA region (Europe, Middle-East, Africa)! It's already been distributed for Beta 1 at VSLive! in San Francisco, but this time we're shipping it inside the box with the upcoming Visual Studio .NET 2005 Beta 2 release all over EMEA.

If you're new here, WeFly247 is a Microsoft project to showcase the new features of Visual Studio .NET 2005, and I'm very proud to have done most of the development for it. In fact, we've been very busy lately closing down the bits for Beta 2, which, as you can imagine, is pretty tough seeing Beta 2 doesn't exist yet ;-)

We'll be running a series of webcasts on the project to get you all excited, so come tune in if you can. Here's the information straight from Learn247:

Learn 247 .NET Learn 247 .NET


We are pleased to announce that shortly we will be releasing the follow up to the highly successful Football247 and WeRock247 projects titled WeFly247.

As per our previous projects we will release a multimedia training DVD that will contain over 60 hours of audio recordings and coding labs on Visual Studio 2005 and developer technologies and full source code to the entire WeFly247 suite of applications.

The WeFly247 scenario focuses on a fictitious airline company and contains everything that a developer needs to know about building applications with Visual Studio 2005 beta 2.

The applications for WeFly247 scenario are broken down into 3 areas covering all areas of development, including Windows Mobile, VSTO, Smart Client, and InfoPath and much more.

  • Pilot: Pre-flight inspection of the aircraft and in-flight monitoring of key systems.
  • Passenger: Entertainment system for in-flight entertainment.
  • Crew: Management system for managing passenger requests.

As we get ready for the launch of the DVD and sample code, throughout April we will be running a series of WebCasts to highlight the technologies we have used in the WeFly247 solutions and give developers previews of the solution which we will release in April.


18 April: 5pm - 6:15pm GMT

WeFly247 Application Walkthrough and Technologies

Come and learn about the WeFly247 project, learn about the technologies used to build the solution and the architecture design.

This session will give you a chance to get an understanding of the entire project and see all the applications working together as well as hear from the developers of the applications that built each application.

AGENDA:

  • WeFly247 Application Overview
  • WeFly247 System Architecture
  • Building the Pilot applications (Windows Forms, VSTO)
  • Building the Passenger application (ASP.NET 2.0)
  • Building the Crew application (.NET CF)
  • Wrap up / Q&A

To access the meeting click here. We recommend connecting 30 minutes before the meeting takes place.


JOINING THE WEBCASTS IS EASY:

Step 1) Make a reminding in your calendar to remind you of the event (do it now so you don't forget).

Step 2) Click the link to join the meeting.

The meeting will take place online, audio will be streamed to your PC.

 © 2005 Learn247.net.     All Rights Reserved.

Blog | Programming | .NET | VS.NET | WeFly247 | Whidbey
Sunday, April 10, 2005 12:10:47 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Checking for .NET 1.1 Service Pack 1 in an MSI#

If you're using a Setup Project to build an MSI installer for your project inside Visual Studio .NET, there are quite some options that allow you to control the installation process. One of the properties you can configure are launch conditions, which allow your installer to check some registry values, installed programs or files to determine if the application can be installed at all.

Now there are a number of cumbersome methods to determine whether service packs are installed on the .NET Framework, but fortunately service pack discovery has been made much easier in .NET 1.1.

If your application requires .NET 1.1 with Service Pack 1 to be installed, you can check the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322 registry key for the SP value (which is a DWORD). If its value doesn't exist, you don't have .NET 1.1 installed; otherwise, it contains the installed Service Pack (or 0 if you don't have any service packs).

Now you can add a Search for the service pack in the installer and store it in a property named "SERVICEPACK":

In the actual launch condition, you can check that property to make sure the service pack has been installed, and point them to the official .NET 1.1 SP1 download page if that's not the case:

The tricky part is the Condition, which is set to NOT(SERVICEPACK="") AND NOT(SERVICEPACK>>"0"). Since MSI prefixes DWORD registry values with a # sign in front of them, the SERVICEPACK property will actually contain a "#0" or "#1" string, not the actual service pack number. So we first check if the property actually exists to check for .NET 1.1 (an empty string would be returned if the key didn't exist in the registry) and then we use the special substring operator >> to make sure the property doesn't end with "0". This way, it will work even for service packs later than SP1 (until SP10 arrives of course, but I don't expect that any time soon).

Now if anybody launches the installer without the required service pack, they will get the following screen prompting them to download it:

Blog | Programming | .NET | VS.NET | Samples
Saturday, April 09, 2005 2:08:04 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

XML Comments Quick Reference#

I'll come out and bluntly admit it: I don't mind writing XML comments in my code. Really. I can get pretty freaky about it too if you let me, and I always try to make sure that every comment is kept in sync with the code it's covering. Why? Because if you make the compiler (or a 3rd party tool for those languages that don't support it) emit an XML comments file, it gives you free and up-to-date IntelliSense and even full-blown technical documentation, duh!

So if you're also constantly lost in the MSDN documentation trying to figure out which tags are allowed and what the syntax is, here's a quick reference for your amusement. Now start writing comments, please!

Documentation Sections

  • <summary>

    A short description of the item; describe the members of the type here.

    <summary>text</summary>

  • <remarks>

    The "Remarks" section of the documentation; specify overview information here.

    <remarks>text</remarks>

  • <example>

    The "Example" section of the documentation.

    <example>text</example>

  • <seealso>

    A link in the "See Also" section of the documentation.

    <seealso cref="member">text</seealso>

Member Documentation

  • <param>

    Describes a parameter to a method.

    <param name="name">text</param>

  • <returns>

    The return value of a method.

    <returns>text</returns>

  • <exception>

    The exceptions that can be thrown.

    <exception [cref="type"]>text</exception>

  • <value>

    Describes the value of a property.

    <value>text</value>

  • <permission>

    The permission applied to a member.

    <permission [cref="type"]>text</permission>

Text Formatting

  • <c>

    Format characters as code within other text.

    <c>code</c>

  • <code>

    Multiline section of code.

    <code>code</code>

  • <see cref>

    A link to a type, member or field in the current compilation environment.

    <see cref="member">text</see>

  • <see langword>

    Undocumented. A link to a keyword in the language of the current compilation environment, e.g. null.

    <see langword="keyword"/>

  • <paramref>

    A reference to a parameter within other text.

    <paramref name="name"/>

  • <list>

    A list of items.

    <list type="[bullet|number|table]">
        <listheader>
            <term>name</term>
            <description>text</description>
        </listheader>
        <item>
            <term>name</term>
            <description>text</description>
        </item>
    </list>

  • <para>

    A paragraph within other text.

    <para>text</para>

  • <include>

    Refer to comments in another file.

    <include file='filename' path='tagpath[@name="id"]' />
Blog | Programming | .NET | VS.NET
Sunday, January 16, 2005 11:00:38 PM (Romance Standard Time, UTC+01:00) #    Comments [14]  | 

 

Tiny but great DataSet change in Whidbey#

There's a quite lengthy post from the VB team about the data design time changes between Whidbey beta 1 and beta 2 (via 3Leaf Development).

In there, I found a little gem that will solve one particular annoyance I have with the current Typed DataSet generator: while the columns are generated as strongly typed objects, they're declared as internal so they're no use outside the declaring assembly. Hence the column names in quotes in my previous posts about DataSets and Web Services:

testData.Employee.Columns["ID"].ColumnMapping = MappingType.Hidden;
testData.Employee.Columns["CompanyID"].ColumnMapping = MappingType.Hidden;

This will fortunately change in Whidbey, allowing more strongly-typed and thus safer code:

testData.Employee.IDColumn.ColumnMapping = MappingType.Hidden;
testData.Employee.CompanyIDColumn.ColumnMapping = MappingType.Hidden;

I had planned a rant on that but it seems I've been pre-empted by the team that figured it out on their own :-) At least, if the C# team is also doing this but I can hardly imagine that they wouldn't follow the pattern here. It's the small things that can really make a difference you know...

Blog | Programming | .NET | VS.NET | Whidbey | Samples
Thursday, January 13, 2005 10:08:58 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Fixing the IExtenderProvider in ASP.NET#

Very, very cool: Fixing the IExtenderProvider in Visual Studio's ASP.NET designer.

The fact that Extenders Providers don't work in ASP.NET has been bothering me for a very long time, and this article shows a way how to fix that once and for all. And all it needs is an extra attribute on your IExtenderProvider class. Sweet!

In case you're wondering: extenders aren't supported in ASP.NET because the team didn't really see a scenario for it. Mainly because they're mostly used to hook into control events and that just doesn't really happen as much on ASP.NET like in WinForms.

But you could still do useful things with it (translation and data binding jump to mind) so I was really bummed about that. I talked to Scott Guthrie about it on the PDC03, explained a few reasons why I'd use them and he agreed I had a good point but it was unlikely that they would actually implement it.

Blog | Programming | .NET | ASP.NET | VS.NET | PDC03
Tuesday, January 11, 2005 12:58:40 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Avoiding WebSite_1 when pulling an ASP.NET Site from SourceSafe#

Today was frustration day. I got all cooked up a few times and got mad at

  • my next-door neighbour (for playing loud music at 2:30 AM)
  • some printer (for paperjamming 66% of the time)
  • Visio (for generally sucking; well either that or I'm just too much of an idiot to understand how to properly draw a UML sequence diagram in there)
  • aspnet_wp (for not finding some system dll's in its own runtime cache until I nuke the process)
  • SourceSafe (see Visio; and for the reason below)
  • Visual Studio .NET 2003 (for pestering me with the "Object type cannot be converted to target Type" CopyLocal bug again)

Luckily, I also picked up what could be the trick to avoid an all-too-common SourceSafe mess.

If you're getting a new solution from SourceSafe that includes an ASP.NET Web Site or Web Service you probably encountered this as well: chances are that you don't keep your sources under IIS's wwwroot but under your local SourceSafe working directory and then create VRoots (Virtual Roots) in IIS to the proper directory. If you did that before you opened the solution in Visual Studio .NET for the first time, you're doomed with a feeble attempt of SourceSafe to create the web for you, again. It notices that there's already a web with that name, creates a new one with the same name and a "_1" suffix and then complains that it can't bind to the proper web site. Here's a trick that worked for us:

  • Create the directory but keep it empty (don't get a local copy out of SourceSafe)
  • Create the VRoot to that directory with the proper name
  • Make sure Anonymous Access is enabled on the VRoot
  • Open the solution in Visual Studio .NET, SourceSafe should be getting the files in the right directory now
  • Disable Anonymous Access on the VRoot if required

If you have other or better solutions, don't hold back and share them :-) I've seen a trick via David to avoid some other problems by making SourceSafe treat a web project as a class library, but I think the friction is pretty high on that one.

And I'll still be holding my breath for the source control system included with Visual Studio 2005 Team System of course. That, and the fact that Visual Studio .NET 2005 doesn't need IIS anymore, or creates web sites under wwwroot by default. Shoosh, now be gone SourceSafe.

Monday, January 10, 2005 10:09:55 PM (Romance Standard Time, UTC+01:00) #    Comments [4]  | 

 

Poor man's generics in .NET 1.x#

I hadn't seen this little .NET 1.x trick before yet, so I thought I'd share it with you: in case you're really hurting from the lack of generics and you can't wait until sometime next year when VS2005 comes out, here you go... Very poor man's unflexible compile-time generics:

using T = MyItem; // It's magic!

public class PoorMansGenerics
{
    public static T[] CreateArray(int size)
    {
        return new T[size]; // Incredible! Just like real generics!
    }
}

Yeah I know it's lame and it does only 2% of what real generics do, but it can save you some typing and I thought it was pretty funny so whatever :-) For example, for a copy/paste ready solution of strongly typed collections: just replace the MyItem stuff below with whatever you want.

using T = MyItem;

/// <summary>A collection of <see cref="MyItem"/> objects.</summary>
public class MyItemCollection : CollectionBase
{
    /// <summary>
    /// Creates a new <see cref="MyItemCollection"/>
    /// </summary>
    public MyItemCollection()
    {
    }
    
    /// <summary>
    /// Adds the given item.
    /// </summary>
    /// <param name="item">The item to add.</param>
    /// <returns>The position in the list where the item was added.</returns>
    public int Add(T item)
    {
        return List.Add(item);
    }

    /// <summary>
    /// Adds the given items.
    /// </summary>
    /// <param name="items">The items to add.</param>
    public void AddRange(T[] items)
    {
        for (int i = 0; i < items.Length; i++)
        {
            List.Add(items[i]);
        }
    }

    /// <summary>
    /// Inserts a given item.
    /// </summary>
    /// <param name="index">The position where to insert the item.</param>
    /// <param name="item">The item to insert.</param>
    public void Insert(int index, T item)
    {
        List.Insert(index, item);
    }

    /// <summary>
    /// Removes a given item.
    /// </summary>
    /// <param name="item">The item to remove.</param>
    public void Remove(T item)
    {
        List.Remove(item);
    }

    /// <summary>
    /// Determines whether this collection contains the given item.
    /// </summary>
    /// <param name="item">The item to search.</param>
    /// <returns><c>true</c> if this collection contains the given item, <c>false</c> otherwise.</returns>
    public bool Contains(T item)
    {
        return List.Contains(item);
    }

    /// <summary>
    /// Gets the index of the given item in the collection.
    /// </summary>
    /// <param name="item">The item for which to retrieve the index.</param>
    /// <returns>The index of the given item in the collection.</returns>
    public int IndexOf(T item)
    {
        return List.IndexOf(item);
    }

    /// <summary>
    /// Copies the elements in this collection to the given array.
    /// </summary>
    /// <param name="array">The array to which to copy the items.</param>
    /// <param name="index">The index at which to start copying.</param>
    public void CopyTo(T[] array, int index)
    {
        List.CopyTo(array, index);
    }

    /// <summary>
    /// Gets or sets the item at a given index.
    /// </summary>
    public T this[int index]
    {
        get
        {
            return (T)List[index];
        }
        set
        {
            List[index] = value;
        }
    }
}

Blog | Programming | .NET | VS.NET | Samples
Monday, November 29, 2004 3:48:21 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

SmartPart for SharePoint v1.0 released#

Jan Tielens - SharePoint Connoisseur Extraordinaire - just released v1.0 of his SmartPart for SharePoint.

"SmartPart: A SharePoint Webpart that can host any ASP.NET user control. Create your webparts by using the VS.NET designer instead of coding everything by hand!"

If you're into Webpart development, you're going to wanna kiss him ;-) Way cool!

Wednesday, November 24, 2004 12:40:07 AM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Guidelines - a hidden feature for the Visual Studio Editor#

Sara Ford (young, cool, smart, goodlooking techie - damn I'm in the wrong country) just revealed a hidden registry setting to enable guidelines in Visual Studio (via Scott Swigart).

Guidelines are just vertical lines that run along a certain column in your text editor. This will of course prove a major productivity gain for COBOL.NET programmers since they're finally able to visually show lines along columns 8 and 12 :-)

Blog | Programming | .NET | VS.NET
Wednesday, November 24, 2004 12:31:15 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Register an assembly in COM+ from Visual Studio .NET#

You know when you have an assembly with serviced components that needs to be registered in COM+, you have to open up a .NET Command Prompt, navigate to the proper directory and call Regsvcs.exe with the name of the dll you want to register?

I just thought of a really simple trick that handles this with two clicks from within Visual Studio .NET (everybody else has probably already figured this out but somehow forgotten to tell me).

Just add an external tool - I cunningly called it "Register Output Assembly In COM+" - with the command pointing to Regsvcs.exe (e.g. "C:\WINNT\Microsoft.NET\Framework\v1.1.4322\RegSvcs.exe") and with "$(TargetPath)" as its arguments.

Now when you run the external tool, it will just register the output assembly of your current project in the COM+ catalog. I also made it use the output window so I see the results right there in Visual Studio .NET as well.

Blog | Programming | .NET | VS.NET
Tuesday, October 19, 2004 2:16:33 PM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

Edit & Continue in C#... Why?#

What's all this fuss about Edit & Continue making it into C#?

I've never really missed it much, and I believe it makes you think harder before you code if you don't have the 'luxury' of coding-as-you-go. I do believe it makes sense in a few scenarios - e.g. when you have a reaaally big project and you don't want to have to recompile for a simple 'i <= length - 1' mistake (but then again, do you really need your project to be that big?) - but in the majority of cases, I just don't see the need.

Thoughts? Prove me wrong!

Blog | Programming | .NET | VS.NET | Whidbey
Sunday, October 17, 2004 8:56:13 PM (Romance Standard Time, UTC+01:00) #    Comments [4]  | 

 

Enhanced feature request about icon sets in Visual Studio 2005#

I've seen on Tom Mertens' blog that Robert McLaws is asking to support the number 2 feature suggestion for Visual Studio 2005: an updated icon set that ships with it. I heartily agree. What's more: I'll top the suggestion and expand it a little.

We don't need just an updated icon set to ship with Visual Studio. If Windows Forms 2.0 is the final stage in Windows Forms development and Microsoft wants people to ship products with a compelling "User Experience" on the road to Longhorn, then they better start providing the community with the proper tools to do so. Icons are in important part of the user experience (platform-wide consistency is an important design principle), so the most important step here is indeed to make the common user interface elements obiquitously available.

In my mind, that's not just shipping the icon files with the development environment. It's also publishing those icons on a searchable resource-like website where you can just download the icon you need without having to search the entire web and skim out the ridiculously outdated or overly charging icon-library websites. Microsoft must already have some central library of these icons to be shared by the product teams so why not share it with the world?

The next step is to go all .NET'y and package a large number of common icons in strongly signed .NET resource assemblies (dll's). These could be distributed with the runtime and placed in the Global Assembly Cache (GAC) so they're readily available to all your .NET programs. That gives you the advantage of being able to update all your icons to the latest (fanciest) version with just a configuration change and it follows the same idea as reusable dll's in the first place: why embed all those common elements in each and every program when you can share one version between multiple programs?

Anyway, an update to the icon set would be a requirement in my mind. The rest would be a nice-to-have. But I really don't want to start searching the web for a decent looking Save button ever again.

Blog | Programming | .NET | VS.NET | Whidbey | WinForms | Windows | Longhorn
Monday, September 27, 2004 1:26:11 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Whidbey Beta 1 Available#

Psssst, don't tell anyone but Visual Studio 2005 Beta 1 is out on MSDN! I can't wait to kick the alpha I'm running now out of the door (sure it's cool and all but it's really messing with my head lately) and install this baby. And if you think that's all, think again...

There's also a range of Express Products (lightweight development tools for non-professionals) which looks interesting if you can't just cough up the dough for the Visual Studio .NET flagship product and related tools. There's even a SQL Server 2005 Express! And there's a Summer Of Express Coding Contest over on Channel9 to promote these little gems...

Some other big news: the new MSDN Product Feedback Center is a place where you can get direct access to the Microsoft bug databases. You can search bugs, report bugs, make suggestions, get notifications if bugs you're monitoring change status, ... Wicked!

Anyway, sorry for bothering you with this - you probably read it all over the blog space today anyway. I just couldn't not post this ;-)

Blog | Programming | .NET | VS.NET | Whidbey
Tuesday, June 29, 2004 6:31:33 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

MoodTip#

In the series "human-pc interaction driving you to deeper metaphysical contemplation" (see MoodPrompting), Visual Studio .NET just enriched my life with a "MoodTip" - a tooltip designed to make you think twice about your software design. Depending on the mood of the environment, it appears that the Controller property is either Overridable (virtual for all you C# boys & girls) or NotOverridable (sealed).

I'll be off to the Himalaya to think this through, I'll let you know when I figured it out...

Blog | Programming | .NET | Quirks | VS.NET
Tuesday, June 15, 2004 3:35:29 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

The Ultimate Reflector Setup#

Why:

  • Because you will need to use it.
  • Because it allows you to search and look at assemblies, classes, methods, fields, inheritance relationships, call stacks, references, resources, ... (let's summarize that as everything).
  • Because you can decompile methods to any supported language (IL, C#, VB.NET, ...).
  • Because it works with any .NET runtime.
  • Because it's cool :-)

Download:

Install Reflector:

  • Unzip Reflector into some random directory on your hard drive (no further installation necessary).
  • Unzip all add-ins to the Reflector directory, open Reflector and add them all (View - Add-Ins - Add).
  • Test: select mscorlib and choose Tools - File Disassembler, click Generate and run through the generated source code to verify that there are no bugs in the .NET Framework (warning: this might be illegal ;-) )
  • Congratulations, you're now ready for some serious .NET development.

Install the Visual Studio .NET add-in:

  • Run Reflector.VisualStudio.exe to install the Visual Studio .NET add-in.
  • Open Visual Studio .NET and enable the Reflector add-in (Tools - Add-in Manager - Check the tool and startup boxes).
  • Test: right click a project and choose Reflect, it should show you a dockable Reflector window.
  • Congratulations, you're now in lazy mode so you never have to leave Visual Studio .NET anymore.

Enjoy:

  • You now have direct access to one of the most useful .NET utilities ever.
  • (It does eat up quite some memory though - 40 MB minimum on my machine, with all add-ins loaded - but it's worth it.)
Blog | Programming | .NET | VS.NET
Sunday, June 06, 2004 1:18:02 PM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

Random Visual Studio Whidbey Annoyances#

I guess I'm in bitch-mode anyway so why not: some random things that are driving me nuts in the Visual Studio .NET Whidbey CTP:

  • Those damn datasets that keep generating an extra SomeDataSet1 file and class! It's great that we get typesafe datasets from the MSDataSetGenerator, really, but one class will do just fine thankyouverymuch. It's probably because the dataset is under source control, thereby making all the associated files readonly. This makes it pretty hard for the generator to write to the file and in stead of checking out the original file, it has the great idea of ignoring it altogether and appending a number to it in stead - thereby introducing multiple versions and breaking builds. (Just my guess.) It even messes things up sometimes if you're just looking at the dataset. What's wrong with looking at a file? Never let looking at a file trigger a checkout!
  • Clicking a project item in the solution explorer when you're coming from another project makes the solution explorer scroll so that the current project node is made visible. Contrary to what someone must have thought on the design team: that's not cool. I don't expect any window to change its position or layout by just clicking in it. But what's a lot worse: if you double-click, say, a classfile and it decides to scroll then the double-click fires afterwards and you're really opening a different file. Combine that with the previous remark and you can have the great effect that double-clicking some regular class in solution explorer can cause the window to scroll, thereby making itself think you clicked a dataset, triggering it to be displayed, checked out and having it generate a SomeDataSet1 file. Did I mention this wasn't a cool feature?
  • And last but not least... The eternal pain of references. This is killing me. References have been bugging me before, so please, please, pretty please with sugar on top, fix them once and for all! Although certainly not always possible, the ultimate solution to problems with references has always been using Project References. So that's what I'm doing now, but in ASP.NET they tend to switch from project references to fixed binary references that don't get updated anymore. And you wonder why you get runtime errors... So you need to check those references regularly and reset them to project references. Joy! What's even funnier is that an ASP.NET site doesn't have a project file anymore, which would be fine if there was nothing to remember for an ASP.NET project. But what about these references? I'm sorry to have noticed that, but they're stored in the solution file. Bad, bad, bad... If you have multiple solutions with the same ASP.NET project in them, it would depend on the solution file which kind of references the project would use. If you really need project-settings, revive the project file guys and recall the "no project file needed"-hype. What's one file extra going to do anyway?

Anyway, ASP.NET 2.0 still pretty much rules apart from that so that makes up for a lot :-)

(That doesn't mean that this shouldn't all get fixed of course, so if you're reading this and can do anything at all about it that would be greatly appreciated :-) )

By the way, I realize it's an alpha build so it's not perfect; this post will just help me re-evaluate the beta once it ships :-)

Blog | Programming | .NET | ASP.NET | VS.NET | WeFly247 | Whidbey
Tuesday, May 04, 2004 9:32:36 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Server Explorer On Steroids#

As you can see they've added a whole bunch of things in the "Server Explorer" window in Visual Studio .NET (Whidbey): basically all your operating system objects are there (processes, treads, accounts, shares, ...). So I guess if you can get to it through WMI then you can find it here. So what used to be a separate download (the WMI Extensions) is now built-in and accessible right from your IDE!

Pop quiz: what's missing in this picture? Yep. The SQL Server plugin. Fortunately, you can still do most of the things you need (editing stored procedures, looking at table definitions, running ad hoc queries) after creating a Data Connection but I must say I panicked for a second there :-) But I'm sure it'll be back when the beta ships...

Now let's hope they fixed that ugly memory protection error I get when trying to browse a table (as I said, most of the things...).

Tuesday, April 27, 2004 4:28:12 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Last week on WeFly247.NET#

Well it's been a bumpy ride so far, I can tell you that. Working with alpha builds is always a bit of a "challenge" so of course we knew we could expect some trouble, but regular crashes and excessive memory consumption are now part of my regular day :-) Let's just say there's still quite a lot of work on Visual Studio, so I think I know why they're calling it "2005" nowadays :-)

So for now, we're still on the CTP build released on MSDN a few weeks ago, but maybe we'll switch over to a newer build if there's a chance of getting a more stable one. Anyway, it's a good thing I'm developing inside a VirtualPC, so when all else fails I can still revert to a more or less clean VPC and just pull the sources from SourceSafe.

But the good news is that there's such a flood of really great stuff coming in Whidbey errrr "Visual Studio .NET 2005 formerly known as Whidbey" (I think I'll stick to calling it "Whidbey" though :-) ) so I'm really excited about it! I've been mostly working on the ASP.NET side of things, and it's just incredible what they've been doing over there. Security, Personalization, DataBinding, Web Parts, Themes, Masterpages, ... It's all there and it's really well architected. And best of all: it even seems to be pretty stable in this build :-)

The trick here is to try and forget some "best practices" from ASP.NET 1.x because there are just a lot of new ways to do things now, and most of them don't require any code at all. Take DataBinding for example, with just 2 lines of markup, you pull your data from your Web Service and bind it to the new GridView control:

<asp:ObjectDataSource ID="infoData" Runat="server" TypeName="WeFly247.UI.Proxies.PassengerWebService" SelectMethod="GetFlightData" />
<asp:GridView ID="info" Runat="Server" DataSourceID="infoData" />

Goodness!

So I'm pretty much done with the general structure of the website, I'm now going to focus on connecting it to the business tier and feeding it some actual live data.

Blog | .NET | ASP.NET | VS.NET | WeFly247 | Whidbey
Monday, April 26, 2004 1:22:23 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Visual MemoryLeak .NET 2005#

I was wondering why my VirtualPC in which I'm running Visual Studio .NET 2005 CTP (f.k.a. Whidbey) got so incredibly slow. I gave the virtual machine 512MB RAM and I only had 2 Visual Studio's open in it (with fairly simple projects), along with an explorer window and a Word document. I did keep the Visual Studio's open for a pretty long time and apparently that's not such a good idea: Windows was using over a gig of memory. One Visual Studio instance took up around 120MB, the other one maxed at 200MB. Talk about a little memory leak... Let's hope this improves in the beta, or - shudder - is it supposed to eat up that amount of memory?

Blog | Programming | .NET | VS.NET | Whidbey
Tuesday, April 20, 2004 3:29:27 PM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Whidbey Impressions#

I've finally started working on the new Whidbey build. Some marketing-friendly people called it CTP (Community Tech Preview), people who like uniformly-rising integers might call it PD4 (Partner Drop 4) but real coders refer to it as "2.0.40301" of course. I've already had it added it to my list of friendly runtimes (don't worry, I still have enough tattoo-space left for years to come).

The first big thing I noticed was that Whitehorse still isn't there. The goodness of having a round-trip engineering class designer, a uml modeller, a network layout modeller, ... all within your IDE (emphasis on the 'I' here) will have to wait a little longer I guess. This means I have to go back to Visio to model my use cases and database diagrams for my current project. It's not that Visio is a bad product or anything, it just sucks that's all. It's not integrated, it doesn't look as good, it feels old and clumsy, and I hate the way it does some of its layouting (especially connectors are terrible, and too hard to correct yourself).

A nice little improvement on the Error Reporting dialog (aka crash-o-spam-a-thon, I always send error reports you know :-) ) caught my attention (actually, it's not a good thing that I noticed this, since I was hoping not to encounter the dialog altogether but well, it's still alpha software of course :-) ): there's now an option that makes the dialog close itself after the error report has been sent. Thanks, it's the little things that annoy you the most you know. They should really put this in the general Windows error reporting dialog as well (I would have figured it was actually the same component but apparently that's not the case).

I got an interesting exception when trying to view a project's properties (officially I'm pretty puzzled how I'm going to manage without project properties but still): "The type serialized in the .resources file was not the same type that the .resources file said it contained. Expected 'System.Drawing.Size' but read 'System.Drawing.Size'". Normally when I get this kind of warning, I'd expect some kind of versioning problem here. But within Visual Studio? Uh-oh...

Update: the Project Properties are working again... There seemed to be a problem with the keyfile causing the build to fail which apparently also prevented the project properties page from loading. I removed the keyfile tag in the .csproj file (xml and msbuild rule) and all is well again.

Now I'm in even more trouble: the new Pocket PC 2003 emulator doesn't seem to do a lot. I doesn't power up and freezes my Visual Studio... Deploying it to the "legacy" emulator does seem to work though, and what's pretty cool is that it gives you these confirmation dialogs to replace "mscoree.dll" so I'm really patching the emulator to the Whidbey build of the .NET CF 2.0. Ah wait a second, fortunately already an update on this: the new emulator does work! You just have to wait a long while when powering it on. It takes a minute before the Windows Mobile splash screen pops up, then you have to wait even longer for the .NET CF 2.0 to be installed, the application itself launches slow, but it works fine and I still think the whole concept rocks :-)

The Object Browser window doesn't seem to have the new view yet where you can see a class definition as an emtpy code file, too bad because I kind of like the idea: it's more natural as a programmer to view a type contract as an actual code block. You can however get a glimpse of how it will look using the (buggy) Windows Forms Class Viewer tool (WinCV.exe in the SDK 2.0 dir).

Update: It's not in the Object Browser window but it's there: View - Other Windows - Code Definition View. This gives you a window that shows a code block for whatever class happens to be under your caret. Cool!

Oh one more thing before I actually get back to work: wear sunglasses when coding. The component tray (you know, the region where your non-visual components like tooltips and extenders are shown in the designer) is in eye-bashing purple. So are the gridlines in the task list. Damn, they must have fired all the people with a working set of eyes over there...

Blog | Programming | .NET | ASP.NET | VS.NET | WeFly247 | Whidbey | WinForms
Thursday, April 08, 2004 3:10:35 PM (Romance Standard Time, UTC+01:00) #    Comments [2]  | 

 

Visual Studio .NET Tips & Tricks#

There's an article on CodeProject with some Tips and Tricks for the Visual Studio .NET IDE. Some pretty nice ones too, actually. Ok somebody in the comments says it's not an actual article since it's just listing some shortcuts you can find in the Help - but who really does that kind of advanced data mining anyway, right ;-)

I should really be using some features more, like incremental search, multiple copy/pastes and cursor position cycling. So many shortcuts, so little time.

Blog | Programming | .NET | VS.NET
Thursday, February 05, 2004 9:20:16 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Visual Studio References#
Another source of getting the wrong version of your code (see my post on Various Caches) can be because your references (real ones, not Project References) in Visual Studio are wrong, so make sure they point to the correct location of the dll. Even if you have set them right before, Visual Studio can still make them go wrong sometimes. And even then, I have noticed that the compiler may use another location than the one you are referencing. Needless to say this can be very frustrating and I haven't found a solution for this yet, sadly. The biggest problem with references is the "copy local" flag (in the reference's properties), which makes a copy of the referenced dll in the bin folder of your project. This makes it easy to run your project because you don't need to register your dll's in the GAC or place them in your DEVPATH. Of course, the drawback is that if they aren’t correctly updated to match the current version (that is, if your reference is wrong), you will be working with an older copy.

If it's in any way possible - and logical within your project - use Project References. I've never seen them go bad.
Blog | Programming | .NET | Quirks | VS.NET
Wednesday, August 06, 2003 8:25:32 AM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

Macro Madness#
There's an article on MSDN that shows some nice things you can do with macros in Visual Studio .NET.

I've never been too fond of writing macros but maybe I'll give two of the examples a go: I liked the one where you select a region of text and put a #region around it in one step, and I was also quite charmed by selecting some private members and automatically generating the public properties for it. These little things might make a difference...
Blog | Programming | .NET | VS.NET
Thursday, June 12, 2003 9:25:55 AM (Romance Standard Time, UTC+01:00) #    Comments [1]  | 

 

ASP.NET Abstract Base Classes & Visual Studio#
Not a good mix ;-) Apparently when you try to open a web form in the designer, it will try to create an instance of the base class of your form's code-behind class. Typically, that will be the System.Web.UI.Page class. I can see why that's a good idea in WinForms where you actually see an instance of a Windows Form (System.Windows.Forms.Form) in the designer - but I don't really understand why the web forms designer would do such a thing. Web forms should render html to the browser, and since User Controls aren't even rendered in the webpage you're designing I don't think any other actual rendering is done by the designer, apart from your own html code for the page of course. So why create an instance of the page's base class?

The reason I came across this is that we have some custom base classes for ASP.NET pages and our design required one of those to be abstract since an overriding class should implement a certain method. Now the designer crashes on us if we derive from that since it tries to instantiate an abstract base class - no more forms designer for that page. Maybe there is a good reason for the designer to instantiate a Page object, but at least it could look further up the class hierarchy for a more suitable (i.e. non-abstract) base class if it really needs some instance to do its work.
Blog | Programming | .NET | ASP.NET | Quirks | VS.NET
Thursday, June 12, 2003 9:03:05 AM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Visual Studio breaking down#
I'm getting extremely pissed at Visual Studio.NET lately. It just keeps bugging me and it keeps getting worse and worse. Furthermore my deadline is getting closer so it's too dangerous to do a full reinstall. So I guess I'll be stuck with the following annoyances for a while:

Debugger

Or "bugger" in short. It looks like the debugger just 'degrades over time'. And now it just seems unstoppable... For instance, I'm trying to set a breakpoint somewhere in a class and Visual Studio opens up another class (with the same name but in another namespace) and tries to set a breakpoint in there. I'm setting a breakpoint in some other class but at runtime it says it cannot break on it because it has no symbols loaded for that class. It does have all the symbols for all other classes in the same project (assembly), but somehow it misses the necessary information for this class. And just now I had to do a hard reset of my pc because the debugger crashed on an Internet Explorer window. No rescue possible, even though it's a Windows 2000 box which can usually recover pretty well from a crash. *sigh*

Compilation Error

Quite often (but randomly) when I run my ASP.NET project, I get a compilation error saying that some random referenced library could not be loaded:

BC31011: Unable to load referenced library 'c:\winnt\microsoft.net\framework\v1.1.4322\temporary asp.net files\webroot\dfead88a\73139fa7\assembly\dl2\3a9f7923\5959d15f_b1fec201\ library.dll': Access denied.

I don't get this; this is a shadowcopy folder managed by ASP.NET and it can't use it properly? I'm hoping it's because this is still .NET 1.1 RC2 but I'm losing so much time killing the aspnet_wp.exe process over and over to get this running. *sigh*

I just restored all .NET content for this web to use ASP.NET 1.0 (which means associating each .NET extension in IIS within the project with the older isapi filter) and so far I haven't gotten the error yet... Keeping my fingers crossed.

Find In Files

Just something I noticed today: I'm searching in files, all project files, all searchable files, whatever, but it seems to keep forgetting to search the root folder of my project. It finds everything in the subfolders but it just completely ignores the root. *sigh*
Blog | Programming | .NET | VS.NET
Wednesday, May 14, 2003 1:02:38 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

All content © 2010, Jelle Druyts
On this page
Updated Again: Setting up Source Server for TFS Builds (v1.2)
Updated: Setting up Source Server for TFS Builds
Setting up Source Server for TFS Builds
Popfly Explorer: Nifty & Smooth!
GAS07: Renaming the solution, signing projects and showing documentation
GAS06: Generating Classes
GAS05: Tuning the C# projects
GAS04: Adding Project References
GAS03: Generating A C# Project
GAS02: Creating A Guidance Package
GAS01: Introduction To The Guidance Automation June 2006 CTP
The Visual Studio 2005 Toolbox, UserControls and Solution Folders
A fix for "Could not access network location" when installing a Visual Studio Addin
Creating a setup package with multiple optional custom actions
Visual Studio 2005 Web Application Projects Released!
Xml Documenter Macro
Article: Customizing generated Web Service proxies in Visual Studio 2005
Setting a default value for a color property
Downloading Visual Studio 2005 RTM
Are you ready for the launch?
Copy To Output Directory
Two Cool Tools
ASP.NET 2.0 References
Deleting source-controlled items
WeFly247 Screenshots
Visual Studio .NET 2005 Beta Experience
Checking for .NET 1.1 Service Pack 1 in an MSI
XML Comments Quick Reference
Tiny but great DataSet change in Whidbey
Fixing the IExtenderProvider in ASP.NET
Avoiding WebSite_1 when pulling an ASP.NET Site from SourceSafe
Poor man's generics in .NET 1.x
SmartPart for SharePoint v1.0 released
Guidelines - a hidden feature for the Visual Studio Editor
Register an assembly in COM+ from Visual Studio .NET
Edit & Continue in C#... Why?
Enhanced feature request about icon sets in Visual Studio 2005
Whidbey Beta 1 Available
MoodTip
The Ultimate Reflector Setup
Random Visual Studio Whidbey Annoyances
Server Explorer On Steroids
Last week on WeFly247.NET
Visual MemoryLeak .NET 2005
Whidbey Impressions
Visual Studio .NET Tips & Tricks
Visual Studio References
Macro Madness
ASP.NET Abstract Base Classes & Visual Studio
Visual Studio breaking down

Recent Photos
www.flickr.com
This is a Flickr badge showing public photos from Jelle Druyts. Make your own badge here.
Advertising
Top Picks
Statistics
Total Posts: 350
This Year: 4
This Month: 2
This Week: 2
Comments: 526
Archives
Sitemap
Disclaimer
This is my personal website, not my boss', not my mother's, and certainly not the pope's. My personal opinions may be irrelevant, inaccurate, boring or even plain wrong, I'm sorry if that makes you feel uncomfortable. But then again, you don't have to read them, I just hope you'll find something interesting here now and then. I'll certainly do my best. But if you don't like it, go read the pope's blog. I'm sure it's fascinating.

Powered by:
newtelligence dasBlog 2.0.7226.0

Sign In