Category name:Winforms

My fight with T4 Templates for BSF

At the moment I’m working on integrating the great Pipeline Component Wizard by Martijn Hoogendoorn into the BizTalk Software Factory. I decided to use a T4 template to get the job done. T4 templates are very powerful and easy to write (but hard to debug, as I found out). This post is dedicated to what I ran into while developing the templates.

The issues I had to deal with were:

  • Changing the wizard into a custom wizard
  • Getting the data out of the wizard into the T4 template
  • Processing the data in the T4 template to get the right code generated
  • Debugging T4 templates
  • Figuring out why I ran into the error “The given key was not present in the dictionary”

     

First of all, the current implementation of the PLCW wasn’t functioning on systems with GAT installed. That was the basic reason to change the wizard. The PLCW is based on the Microsoft.BizTalk.Wizard framework. The custom wizard pages in the guidance inherit from the Microsoft.Practices.WizardFramework.CustomWizardPage class and the wizard of the PCLW couldn’t be used 1-on-1. One of the reasons for that is that it is important for the CustomWizard page to have the following constructor:

public MyCustomWizardPage(WizardForm parent) : base(parent)
{
   // This call is required by the Windows Form Designer.
   InitializeComponent();
}

Among other things like the way the wizard saves state between pages, I decided to reuse the pages but inherit them from the CustomWizardPage.

The wizard collects some data to use in the pipeline component creation process and everybody who is familiar with the PLCW knows you can define custom design properties at the end of the wizard. I thought it was a good idea to create a small class for that to contain the type/name values and put that into a generic list for ease of use.

For every variable you want the wizard save between pages and have available at the end, you should define an argument in your recipe. So I defined like this:

<Argument Name="ClassName" Type="System.String" />
<Argument Name="DesignPropertyList" Type="System.Collections.Generic.List<MyDesignProperty>" />
 

Everybody with a bit of XML knowledge understands that this isn’t going to work, so I looked at some samples and changed it into this:

<Argument Name="DesignPropertyList" Type="System.Collections.Generic.List&lt;MyDesignProperty&gt;" />
 

In the code of the custom wizard pages, you can write and read the arguments like this. Make sure the name used in ‘SetValue’ is exactly the same as the argument.

IDictionaryService dictionaryService = GetService(typeof(IDictionaryService)) as IDictionaryService;
dictionaryService.SetValue("ClassName", txtClassName.Text);
 

  • Make sure every argument has a value otherwise you will end up with error messages saying the specific argument is empty.

So now I have defined the necessary arguments and created the custom wizard pages and now I want to use the arguments in a T4 template action. For that I need in fact 2 actions:

  • Generate the content with Microsoft.Practices.RecipeFramework.VisualStudio.Library.Templates.TextTemplateAction
  • Write the content to file with Microsoft.Practices.RecipeFramework.Library.Actions.AddItemFromStringAction

I defined the arguments as properties in the T4 template, but it didn’t work. I didn’t receive the generic list with design property values as I saved into the arguments in the wizard. After a long search I found that a generic class isn’t supported by this action. How to solve this? I downloaded the Smart Client Software Factory and the Web Services Software Factory to peek at their solution (they are quite good at this, I can tell you that). There appeared to be a simple solution for this like defining an empty new ‘collection’ class like this:

public class MyDesignPropertyCollection : List<MyDesignProperty>
{
}

So I had to change the argument again into:

 <Argument Name="DesignPropertyList" Type="BizTalkSoftwareFactory.BusinessEntities.MyDesignPropertyCollection, BizTalkSoftwareFactory" />

In the T4 template the value should be specified as this:

<#@ Assembly Name="BizTalkSoftwareFactory.dll" #>
<#@ Import Namespace="BizTalkSoftwareFactory.BusinessEntities" #>
<#@ property processor="PropertyProcessor" name="DesignPropertyList" #>
 

One of the other issues is that I was uncertain what I received in the T4 template. Debugging T4 templates can be hard although there seems to be tools available. I used a custom debug class to be able to view the available values in the template. I defined a method which I call in the beginning of the template:

<#  TemplateHelper.DebugThis(this); #>

Of course you need to import the namespace where the method can be found:

 <#@ Import Namespace="BizTalkSoftwareFactory.BusinessComponents" #>

So I now have generated content which I need to write to file, that is what the second action is for. It takes the output of the previous action, location to add the file to and name of the file. That doesn’t look too difficult as I used it already in the Unit test part. So I copied that part of the recipe and then I ran into the error “The given key was not present in the dictionary” and I really didn’t understand where it came from. I compared the code and it looked the same. At these times it can be very frustrating not to be able to debug, as I found out I didn’t even get into the second action. I grabbed the code using reflector and created my custom action with that code, but the error showed up even before entering the action.

Now what?

Literally hours later I found what caused the action and of course afterwards it is always easy.

I defined the action like this:

 <Action Name="AddPipelineComponent" Type="Microsoft.Practices.RecipeFramework.Library.Actions.AddItemFromStringAction, Microsoft.Practices.RecipeFramework.Library" Open="true" >
  <Input Name="Content" ActionOutput="GeneratePipelineComponent.Content" />
  <Input Name="TargetFileName" RecipeArgument="PipelineComponentFileName" />
  <Input Name="Project" ActionOutput="PipelineComponentProject" />
</Action>

The problem is in the “ActionOutput” for input “Project”. Logically “ActionOutput” means……..output of an action and that wasn’t the case. It was a regular “RecipeArgument”. Very easy to fix, but hard to find.

So if you run into the error “The given key was not present in the dictionary”, think about this it will save you hours. J

If you need guidance with generic lists and wizards/actions I would advise you to look at the Smart client software factory and the Web service Software Factory.

Winforms: web browser control trouble

The last couple of days I had a big fight with the web browser control that ships with Visual Studio 2005 and the .NET framework 2.0.


I was using the ‘DocumentText’ property to display some HTML in the control, but for some dark reason it refused to do that. Instead it just contained <HTML></HTML> after assigning some HTML text to it. The strange thing was that everything worked fine after assigning HTML text to it the second time, but doing that the first time failed for some reason.


At the end I was very happy to find a blogpost with the solution: C# 2.0 WebBrowser control – bug in DocumentText?

Clearing a listview

Sometimes you run into a problem you just can’t seem to find a solution for because the problem doesn’t make sense.


Today I had such a problem. At home I’m working on a RSS reader home project. I have a listview to show all posts of a certain feed. Just before I fill the feed I clear the contents of the view. The listview is in details-view.


I used “listview.clear” to remove all items from the listview. But runtime I noticed that the listview remained empty while the items where added correctly. I didn’t know it was that statement causing the problem, so where to start looking?


In these cases Google is my best friend and at the end I found the solution in a newsgroup.


The statement “listview.clear” also removes the column headers, that is why the listview apeared to remain empty. I had to clear the “items” collection of the listview in order to fix it, so I changed the code into “listview.items.clear” and everything worked fine again.