Label Cloud

Friday, December 21, 2007

Closing and Terminating views in SCSF

There is a bug in the SCSF implementation that views even though closed, will not terminate the workitems. This was described on codeplex in the following thread:

Here is my workaround:

In the View.Designer.cs

protected override void Dispose(bool disposing)
if (disposing)
if (_presenter != null)
_presenter.OnCloseView(); // <<<<<<<<======= Workaround

if (components != null)


In the ViewPresenter.cs

bool Closing = false;
public void OnCloseView()
if (!Closing)
Closing = true;
if (WorkItem.Status != WorkItemStatus.Terminated)


The code will force the workitem to be terminated after the view is closed

Technorati Tags: , ,


Thursday, December 20, 2007

Compiling .NET 3.5 code to .NET 2.0 Works

This is something that is totally cool. You can use Visual Studio 2008 and a lot of the new functionality and cross compile it to .NET 2.0 and run it on the older framework. For Example, You can use Var objects, Simple Property Declarations, Property Constructors, Lambda expressions

Here's an example program that can be compiled with VS 2008 to the .NET 2.0 framework

static class Program
        private class Client
            public string Name { get; set; }
            public string Address { get; set; }

        private static List<Client> clients = new List<Client>
            new Client() {Name = "Name1", Address = "Address1" },
            new Client() {Name = "Name2", Address = "Address2" },
            new Client() {Name = "Name3", Address = "Address3" },
            new Client() {Name = "Name13", Address = "Address13" },
            new Client() {Name = "Name123", Address = "Address123" }

        static void Main()
            List<Client> ClientsWith1 = clients.FindAll(c => c.Name.Contains("1"));
            ClientsWith1.ForEach(c =>
                var NewClient = new
                    Name = c.Name,
                    Address = c.Address
Here's the output

{ Name = Name1, Address = Address1 }
{ Name = Name13, Address = Address13 }
{ Name = Name123, Address = Address123 }

And it works without .NET 3.5 installed.

For those interested, Here's a Reflected code

internal static class Program
    // Fields
    private static List<Client> clients;
    private static Predicate<Client> CS$<>9__CachedAnonymousMethodDelegate2;
    private static Action<Client> CS$<>9__CachedAnonymousMethodDelegate3;

    // Methods
    static Program()
        List<Client> <>g__initLocal4 = new List<Client>();
        Client <>g__initLocal5 = new Client();
        <>g__initLocal5.Name = "Name1";
        <>g__initLocal5.Address = "Address1";
        Client <>g__initLocal6 = new Client();
        <>g__initLocal6.Name = "Name2";
        <>g__initLocal6.Address = "Address2";
        Client <>g__initLocal7 = new Client();
        <>g__initLocal7.Name = "Name3";
        <>g__initLocal7.Address = "Address3";
        Client <>g__initLocal8 = new Client();
        <>g__initLocal8.Name = "Name13";
        <>g__initLocal8.Address = "Address13";
        Client <>g__initLocal9 = new Client();
        <>g__initLocal9.Name = "Name123";
        <>g__initLocal9.Address = "Address123";
        clients = <>g__initLocal4;

    private static void Main()
        if (CS$<>9__CachedAnonymousMethodDelegate2 == null)
            CS$<>9__CachedAnonymousMethodDelegate2 = delegate (Client c) {
                return c.Name.Contains("1");
        if (CS$<>9__CachedAnonymousMethodDelegate3 == null)
            CS$<>9__CachedAnonymousMethodDelegate3 = delegate (Client c) {
                Console.WriteLine(new { Name = c.Name, Address = c.Address }.ToString());

    // Nested Types
    private class Client
        // Fields
        private string <Address>k__BackingField;
        private string <Name>k__BackingField;

        // Properties
        public string Address
                return this.<Address>k__BackingField;
                this.<Address>k__BackingField = value;

        public string Name
                return this.<Name>k__BackingField;
                this.<Name>k__BackingField = value;
Technorati Tags: ,,,,


Calculating Hashvalues for files the way .NET does for the Application Manifest


I had to write a custom download component to download modules for a ClickOnce deployed application. The actual downloading is simple, the tricky part was creating the manifest and make sure that I only download files that are required.

I am using an GeneraApplicationManifest MSBuild task to generate an application manifest. The documentation is very easy to follow.

The generated manifest will include a Hash value. It is fairly simple to compute the same hash value manually and be able to validate it.

private bool HashChanged(string fileName, string originalHashValue)
            byte[] Hash = Convert.FromBase64String(originalHashValue));
            byte[] newHash;
            SHA1Managed sha = new SHA1Managed();
            FileStream strm = null;
                strm = new FileStream(fileName, FileMode.Open, FileAccess.Read);         
                newHash = sha.ComputeHash(strm);
                if (strm != null)

            if (Hash.Length != newHash.Length)
                return true;
            for(int i = 0; i< Hash.Length; i++)
                if (Hash[i] != newHash[i])
                    return true;
            return false;
Technorati Tags: ,,,


Wednesday, December 05, 2007

Subversion pre-revprop-change hook

Another small batch file hook for those running subversion on windows. This hook will allow users to update a log message on the old check-in.

Note: Property changes are not versioned, so you will permanently loose the old message.

Place this in a pre-revprop-change.cmd
IF %4 EQU svn:log GOTO OK
echo "Changing revision properties other than svn:log is prohibited" >&2
exit 1
exit 0


Technorati Tags: , ,


Creating a ClickOnce Smart Client CAB Based (SCSF) application with Environment Overrides

My application is distributed via ClickOnce and a requirement is to be able to provide endpoint overrides for multiple environments. Here's what was done to create the solution

Smart Client Software Factory includes a service called EndpointCatalog. It allow for easy management of endpoints with environment overrides. Start by adding a Microsoft.Practices.SmartClient.EndpointCatalog.dll as a reference to Infrastructure.Module. Then open ModuleController class in and register the EndpointCatalog service.

private void AddServices()
IEndpointCatalog catalog = WorkItem.RootWorkItem.Services.Get<IEndpointCatalog>(false);
if (catalog == null)
IEndpointCatalogFactory catalogFactory =
new EndpointCatalogFactory("Endpoints");

catalog = catalogFactory.CreateCatalog();

This will read the endpoint catalog from the application.config file , section Endpoints. Here is a partial app.config

<section name="Endpoints" type="Microsoft.Practices.SmartClient.EndpointCatalog.Configuration.EndpointSection, Microsoft.Practices.SmartClient.EndpointCatalog" />

<add Name="DataService.DataClient"
UserName="default-user-name" Password="default-password" Domain="default-domain">
<add Name="QA" Address="http://qa.server/DataService.svc"/>
<add Name="UAT" Address="http://uat.server/DataService.svc"/>

The endpoints section defines an endpoint, and an override for each environment. The catalog will return the override if it exists, or the original entry if it does not.

To create the WCF client, I created the following function.

T CreateWCFClient<T, Ti>()
where T : ClientBase<Ti>, new()
where Ti : class
T client = new T();
if (endpointCatalog.EndpointExists(typeof(T).FullName))
client.Endpoint.Address = new EndpointAddress
(endpointCatalog.GetAddressForEndpoint(typeof(T).FullName, Environment));
return client;
catch (Exception)

Add the required service to the module that will hold the function and you are almost done. The request to create the client is as follows.

_DataWebService = CreateWCFClient<DataService.DataClient, DataService.IDataClient>();

Please comment for any questions, I'll try to clarify


Tuesday, December 04, 2007 redirection

I finally implemented a javascript redirection on the to


Chat with me

Something new I've just added to my blog webpage, the "Chat with me" link. It is in the right top corner of the site.


You can implement this functionality by


Thursday, November 29, 2007

Google Maps for Mobile - Impressions Day 2

I just took a 60 mile trip and was checking My Location on the Google Maps for Mobile. To my surprise, it was pretty accurate during the whole trip. The map would place me on the highway or within a 50 yards of the highway along the route.

Another interesting detail, I didn't have to request the location all the time, the position would adjust as the car was moving - very much like the it does on the GPS.

That brought an interesting thought - Now Google has not only the locations where their devices are being used, but the movement patterns as well. That's a lot of information that can be used for both good and not ...

Technorati Tags: , , ,


Wednesday, November 28, 2007

Google Maps for Mobile - now with My Location

Today, Google made a new Google Maps for Mobile available on their site. The new beta version includes a My Location functionality that places your location on the map based on the information received from the cell phone towers.

I've installed the app on my Blackberry Curve. The My Location function is great, even though it is not as good as a GPS. While in my office, Google Maps placed my location within around 100m of my real location. That was great. The neat trick is that positioning works even when you are inside a building, something that GPS can not do.

However, near my house, it around 1 mile off (1.5 Km). Still good, considering makes it easier to request directions since its a lot easier to select the source point on the map.

I am sure they will be improving the app as time goes on as well.

Get the application from

Technorati Tags: , , ,


Thursday, November 22, 2007

Subversion pre-commit hooks

I am using a wonderful source control product Subversion. More information is available on

A great future of subversion is an ability to run a server script before the check-in is committed . The script has the ability to rollback the check-in. We are using this functionality to enforce comments for every check-in.

To create a script, place any executable file into a hooks folder in the repository. You can start with a pre-commit.tmpl template file that is located there already. The template is a perl script that accomplishes exactly that functionality.

Our subversion runs on a windows system, so we converted the perl script into a batch file

"c:\Program Files\Subversion\bin\svnlook.exe" log -t %2 %1 | FindStr [a-zA-Z0-9]
echo "Commit Comments are Required" >&2
exit 1
exit 0

Place the code into a pre-commit.bat batch file in the hooks directory.

Technorati Tags: ,,,


Monday, November 19, 2007

VS 2008 Wait is over

VS 2008 is RTM on November 19th

Get it now...

Technorati Tags: , ,


Slacking around with

A great music service is provides an Internet radio with a few great twists.

  1. It is customizable. You can create your own radio stations by flagging favorite artists.
  2. You can play it through the Web Page or via a installed music player
  3. It lets you take the radio with you by letting you buy a portable music player.

This is definitely something that I am looking forward. Some things from my wish lists are

  1. Ability to tap into an ITunes music library for extended selection
  2. Ability to use a cell phone network for data transfer, so I am not tied to the WiFi or their proprietary DJ Technology (Can someone please explain that to me)
  3. Bluetooth in the portable device

So far, I am loving it.

Technorati Tags: , , ,


Wednesday, November 07, 2007

Windows Live Writer is out of Beta

The excellent tool that I am using to write blog entries is finally out of beta. Get it now.

Technorati Tags: , ,


Tuesday, November 06, 2007

Compiling an empty project from Command Line (Compiler Error CS2008)

I've encountered an interesting issue compiling a WebSite Project from command line using msbuild.

The project did not have any .cs source files because the pages referenced assemblies from a different project. Compilation was successful in Visual Studio, but using msbuild, I was getting a compiler error CS2008 - No inputs specified

Apparently, Visual Studio handles an empty project properly, but command line csc compiler does not. To solve the issue, just add a blank .cs file (I actually placed the comment explaining the issue in the file)

Technorati Tags: , , ,


Tuesday, October 23, 2007

Removing (Uninstalling) ClickOnce Web deployed application from Application Cache

I keep looking up a command to completely uninstall a ClickOnce application form the Application Cache. If the application is deployed using "Run from the Web" method, it is not available in the "Add/Remove Programs" control panel. To remove it, you have to clear the application cache. An important note: this will remove ALL applications from the cache. That is usually not a problem, since the next time you run them, they  will be downloaded.

To clear the cache, run the command "mage.exe -cc" from the Visual Studio command prompt.

Mage is using an API call to accomplish this functionality

CleanOnlineAppCache from Dfshim.dll

You can achieve the same functionality by using a command line tool RunDll32.exe

rundll32 %windir%\system32\dfshim.dll

Or create a batch file with the above command

Technorati Tags: , , ,


Wednesday, October 17, 2007

SecureString class

I found a reference to an interesting class while reading MS blogs. System.Security.SecureString might come in handy in whenever you want to keep strings in memory securely.

Technorati Tags: , ,


Monday, October 15, 2007

Software Quote

I am rereading the Code Complete book again. And this quote is just perfect

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult. -C. A. R. Hoare

Someone should write a book "Refactoring to simple" or "Designing so you don't have too"

Technorati Tags: ,


Saturday, October 13, 2007

Providing multiple endpoints for the WCF service

I had to implement compression for an internal WCF service. A requirement however is to make sure that older version of the service is left as is. To achieve that we've added another endpoint to the an existing binding

Here's the original Configuration File for the server.

<services> <service name="Repositories.Clients" behaviorConfiguration="DebugBehavior"> <endpoint name="Clients" contract="IClients" binding="basicHttpBinding" /> </service> <services> <behaviors> <serviceBehaviors> <behavior name="DebugBehavior"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors>

The URL for the service is http://hostname/Clients.svc

Here's the new file

<services> <service name="Repositories.Clients" behaviorConfiguration="DebugBehavior"> <endpoint name="Clients" contract="IClients" binding="basicHttpBinding" /> <endpoint name="ClientsCompressed" contract="IClients" bindingConfiguration="compressedConfiguration" binding="customBinding" address="compressed"/> </service> <services> <bindings> <customBinding> <binding name="compressedConfiguration"> <compression compressionMode="GZip" compressionLevel="Normal"/> <httpTransport/> </binding> </customBinding> </bindings> <behaviors> <serviceBehaviors> <behavior name="DebugBehavior"> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true" /> </behavior> </serviceBehaviors> </behaviors>

The new URL for the service is http://hostname/Clients.svcc/compressed

The old service works just like before. But the new one requires compressed data. It is serviced by a completely separate binding and has independent configuration.


You can follow the same above example to specify multiple client endpoints as well. However, if multiple endpoints exist, the WCF Proxy has to be created by specifying an endpoint name.


localhost.Clients1Client client = new localhost.Clients1Client("compressed");

Technorati Tags: , ,


Tuesday, October 02, 2007

Working with Java (pure Java)

Last couple of weeks I was writing a lot of code for a Proof Of Concept project. Having to switch from my "native" C# to Java brought out a very interesting experience. Here are some high lights

  • Java is not as evil as it might seems at first. - It is fast, clean and has LOTS and LOTS of libraries and tools available to support your work.
  • It is very possible port logic from GOOD C# code to java code. I am talking about server side code, not WinForms. The development style is very similar. The APIs and Frameworks are so similar, that often the difference in code is only the casing of the function calls.
  • Eclipse Rocks! - So far, I've worked in eclipse, and am totally loving it. One one hand it is a very easy to understand UI with great support for development. On the other hand, it has more options and variations then any other development tool I've ever seen. I wish someone would have adopted it for .NET development.
  • Some things I still don't understand and / or miss. For example - Properties definition instead of using Getters/Setters. Ability to write services. I am still not sure how to run Java daemons in a Unix environment, but for Windows they have to be wrapped into 3rd party tools.
  • It seems that Garbage Collection is implemented better in .NET. I've never had to worry about maximum and minimum heap sized, or freezes within my application for GC run. It seems that those are often the topic sand concerns of the Java world. I've hit the "Out of memory" exceptions on multiple occasions so already. The only time I've ever received one in .NET was with a recursion bug.

Overall. The experience of writing in a different language is excellent. It gives you a very different (and often the same) perspective on software development. I probably would have the chance to learn a new language if I can't apply it, however, if there is a way to apply a new language - I would say - Go for it.

Technorati Tags: , ,


Monday, September 17, 2007

System freeze and SATA Command Queuing

After building the new PC, I've been experiencing constant system freezes. Windows would freeze for about 30 seconds - all keys including CTRL+ALT+DEL or CTRL+SHIFT+ESC that usually switch to the appropriate utilities would work. 30 seconds later, the system would go back to its regular state, like nothing would happen. All actions that where queued up during the 30 second "freeze" would fire up. This was happing during performance intensive operations as well as when the computer was relatively free, on average around twice an hour.

After upgrading all drivers, motherboard BIOS and removing disconnecting all peripherals - I finally found a solution on Google that worked. Apparently - NVidia SATA drivers enable Command Queuing by default even though many drives do not support it. Disabling the option seem have fixed the problem. I've been "Freeze" free for 4 days now.

To see uncheck the option:

  • Right click on computer, select "Properties
  • Select "Device Manager"
  • Find "Storage Controllers"
  • For each "NVIDIA nForce Serial ATA Controller" select Properties, and uncheck "Enable Command Queuing" option




Thursday, September 13, 2007

Grouping emails by Conversation

When reading emails in GMail, they are organized by conversation so related emails are easy to see. Follow the following steps to create the same view in outlook.

  • Select View -> Arrange By -> Current View -> Define Views
  • Select "Messages" and click "Copy..." button
  • Rename the new view to be "Messages by Conversation
  • On the new view, select "Modify...", then "Group By..."
  • Select "Conversation" in the first group by drop down.
  • Hit OK to apply the view


Technorati Tags: ,


Asynchronous ForEach (Part 2)

I wrote about creating an asynchronous ForEach method before. Apparently, there is a going to be a TPL (Task Parallel Library) available that will do the same type of functionality (and more I am sure)

Parallel.For(0, 100, delegate(int i) { a[i] = a[i]*a[i]; });

Technorati Tags: ,


Wednesday, September 12, 2007

Building a new PC

Recently, the Hard Drive on my old Dell Dimension 3000 died, and I've decided to upgrade. Seeing that Scott Hanselman just recently got a PC upgrade, and Jeff Atwood wrote a few great articles on building that PC, I've decided to build a PC as well.

Here are the specs with NewEgg links (I got everything from NewEgg)



MSI P6N SLI Platinum LGA 775 NVIDIA nForce 650i SLI ATX Intel Motherboard - Retail
Item #: N82E16813130081


Intel Core 2 Duo E6550 Conroe 2.33GHz LGA 775 Processor Model BX80557E6550 - Retail
Item #: N82E16819115030


MSI NX8600GT-T2D256EZ GeForce 8600GT 256MB 128-bit GDDR3 PCI Express x16 SLI Supported Video Card - Retail
Item #: N82E16814127293

Hard Drive

Western Digital Caviar SE16 WD5000AAKS 500GB 7200 RPM SATA 3.0Gb/s Hard Drive - OEM
Item #: N82E16822136073


Patriot Extreme Performance 2GB (2 x 1GB) 240-Pin DDR2 SDRAM DDR2 800 (PC2 6400) Dual Channel Kit Desktop Memory Model PDC22G6400LLK - Retail
Item #: N82E16820220144

CPU Cooler

Scythe SCNJ-1100P 120mm Sleeve CPU Cooler - Retail
Item #: N82E16835185038


RAIDMAX SMILODON ATX-612WBP Black SECC STEEL ATX Mid Tower Computer Case 500W Power Supply - Retail
Item #: N82E16811156062








Grand Total


Everything got delivered in 3 days.

The whole build took around 2 hours. I had a few minor hiccups - one is that I didn't expect the CPU cooler to be as big as it was. It really is a monster. After putting it on the mother board, I could not close the case since the heat-sink would hit the case power supply. I had to take CPU cooler off, close the side of the case with the mother board, and place the CPU cooler back.

The other hiccup was that the case came with a 4 pin power cord for CPU power. The motherboard had an 8 pin socket. However, after looking on the MSI website, I aw that you can use the 4 pin plug in one side of the socket.

Here are the final pics:




Overall, computer rocks. Fast, stable, looks cool :) The whole experience was pretty painless (if you follow the directions)


Wednesday, August 29, 2007

FireFox and NTLM authentication

I am trying to use Firefox as my main browser, and one of the annoyances is that FireFox does not use NTLM authentication by default. Here are the steps to enable the NTLM authentication

  • In the address bar, navigate to about:config
  • Filter for ntlm
  • modify the value of network.automatic-ntlm-auth.trusted-uris
  • enter comma separated list of URIs that require NTLM authentication in the format http://server1,http://intranet

That's all


Friday, August 24, 2007

Update to the Scott Hanselman's 2007 Ultimate Developer and Power Users Tool List for Windows

Scott Hanselman updated his excellent tool list. For those who never saw it, I highly recommend to go through the list to see what they have been missing. For those who know about it, see what's new.


Friday, August 17, 2007

Tweaking RuleSetDialog to Resize

I've started working with Windows Workflow Rulesets to apply dynamically apply business rules to some internal processes. This involves using External RuleSet Toolset from Microsoft samples (some more on that in a later post)

One of the biggest peeves about the RuleSetDialog is its inability to resize. Viewing a complex rule in a three line window is very uncomfortable. It is, however, pretty easy to tweak the dialog and make it a lot more user friendly.

External RuleSet editor comes in source code. Open the code using Visual Studio, open the code for the RuleSetEditor form and find the editButton_Click event. What you'll may notice is that the RuleSetDialog class used derives from Dialog. Add a new function that will adjust the dialog to make it resizable.

private void TweakRuleSetDialogToResizable(RuleSetDialog ruleSetDialog) { ruleSetDialog.FormBorderStyle = FormBorderStyle.Sizable; ruleSetDialog.HelpButton = false; ruleSetDialog.MaximizeBox = true; ruleSetDialog.Controls["okCancelTableLayoutPanel"].Anchor = AnchorStyles.Right | AnchorStyles.Bottom; ruleSetDialog.Controls["rulesGroupBox"].Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; ruleSetDialog.Controls["ruleGroupBox"].Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom; ruleSetDialog.Controls["ruleGroupBox"].Controls["thenTextBox"].Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom; ruleSetDialog.Controls["ruleGroupBox"].Controls["elseLabel"].Anchor = AnchorStyles.Left | AnchorStyles.Bottom; ruleSetDialog.Controls["ruleGroupBox"].Controls["elseTextBox"].Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom; ruleSetDialog.Controls["ruleGroupBox"].Controls["conditionTextBox"].Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; ruleSetDialog.Controls["rulesGroupBox"].Controls["panel1"].Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; ruleSetDialog.Controls["rulesGroupBox"].Controls["panel1"].Controls["chainingBehaviourComboBox"].Anchor = AnchorStyles.Top | AnchorStyles.Right; ruleSetDialog.Controls["rulesGroupBox"].Controls["panel1"].Controls["chainingLabel"].Anchor = AnchorStyles.Top | AnchorStyles.Right; }

Then call the function passing the RuleSetDialog before it is display from the editButton_Click event.

RuleSetDialog ruleSetDialog = new RuleSetDialog(selectedRuleSetData.Activity, null, selectedRuleSetData.RuleSet); //Tweak the RuleSetDialog TweakRuleSetDialogToResizable(ruleSetDialog); DialogResult result = ruleSetDialog.ShowDialog();


Thursday, June 14, 2007

Alternatives to Enum(s)

I had the need to provide an enumerated value to in my code, and be able to easily convert the value to a string representation. .NET Enum can only be numeric and did not provide the functionality needed. A coworker showed me very nice way to create the same functionality with a simple class

//Using this instead of an ENum - beeing fancy internal sealed class ActionType { private readonly string _action; private ActionType(string action) { this._action = action; } public override string ToString() { return _action; } public static readonly ActionType Update = new ActionType("U"); public static readonly ActionType Delete = new ActionType("D"); }

What the class allows me to do is the following:

main() { ActionType action = ActionType.Update; action = ActionType.Delete; string sAction = action.ToString(); }

sAction will have "D" as the string representation for the Delete action.


Friday, June 08, 2007

.CHM help files are not working

Sometimes I download help files from the Internet, and on some machines, they would not display. I'd receive an IE error "The page cannot be displayed".

I finally decided to look into it. The solution is explained in the KB902225

The specific thing that worked for me was:

  1. Right-click the CHM file, and then click Properties.
  2. Click Unblock.
  3. Double-click the .chm file to open the file.


Thursday, June 07, 2007

Making your assemblies describe themselves

One of the biggest hurdles of the release process is to make sure you know exactly what you are releasing. To make that job a little easier, I've modified my project files to generate and include build related information in the assembly properties.

I am using a great open source project MSBuild Community Tasks.

Three of the tasks included are Time, Version and AssemblyInfo. Here's the process to incorporate them into the project.

Add a new project to the visual studio solution. I made this a C# project to make it easier to integrate with Visual Studio. Then open the project in your favorite text editor. Scroll down to the <Target Name="Build"> line. Now make the contents of the target the following

<Time Format="yyyy/MM/dd HH:mm:ss">
<Output TaskParameter="FormattedTime" PropertyName="buildDate" />
<AssemblyInfo CodeLanguage="CS" OutputFile="GlobalInfo.cs"
AssemblyDescription="Build Date: $(buildDate)
Configuration: $(Configuration)$(Platform)" />
<Version VersionFile="version.txt">
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Build" PropertyName="Build" />
<Output TaskParameter="Revision" PropertyName="Revision" />
<AssemblyInfo CodeLanguage="CS" OutputFile="AssemblyVersion.cs" AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)" AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)" />

Now add a new text file to the project called version.txt and edit to have a single line ""

That is almost it. After compiling the above project, you will receive two new files: GlobalInfo.cs and AssemblyVersion.cs. GlobalInfo.cs will contain the BuildDate and Configuration used during compilation. AssemblyVersion.cs will contain the version information based on the version.txt file. See help for AssemblyVersion task for how to make it increment the version number during the build.

Another task is to add the two new files as a replacement to the AssemblyInfo.cs that's usually a part of every solution. I do it with a text editor to make sure that they point to the file outside of the local project but rather to the newly generated files. That makes files read-only. The last task is to make sure that build dependency is properly set and the "GenerateVersion" project will be built first.

What you achieve after doing all of the above is that all compiled assemblies (.DLL and .EXE) will have a shared version number across multiple assemblies. They will also have in their properties tab information on when they where built and the configuration used during built. That can be used to troubleshoot and to easy production deployments.


Monday, June 04, 2007

Using CodeDOM to generate parsing classes

I needed to create a simple dynamic parser to create a class based on an array of objects into a class. This would be used to read a data from a DatReader. My first thought was to use reflection but having a little bit of spare time, I decided to make a sample of doing this using CodeDOM.

My data class:

namespace CodeDomTest { public class DataClass { private int _FirstInt; private long _SecondInt; private string _FirstString; private string _SecondString; public int FirstInt { get { return _FirstInt; } set { _FirstInt = value; } } public long SecondInt { get { return _SecondInt; } set { _SecondInt = value; } } public string FirstString { get { return _FirstString; } set { _FirstString = value; } } public string SecondString { get { return _SecondString; } set { _SecondString = value; } } } }

And the Interface that the parser would have to implement:


namespace CodeDomTest { public interface IParser { DataClass Parse(object[] s); } }

I started working backwards, and wrote a simple parser myself:

class StringParser : IParser { public DataClass Parse(object[] s) { DataClass _data = new DataClass(); _data.FirstInt = System.Convert.ToInt32(s[0]); _data.FirstString = System.Convert.ToString(s[1]); _data.SecondInt = System.Convert.ToInt32(s[2]); _data.SecondString = System.Convert.ToString(s[3]); return _data; } }

Now, all I needed is for the CodeDom to generate the above code, and I am done. Easy...


using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text; using Microsoft.CSharp; namespace CodeDomTest { class DataParserGenerator { private static Dictionary<Type, IParser> ParserCache = new Dictionary<Type, IParser>(); private static object SyncLock = new object(); public static IParser GetCachedParser(Type ReturnType, string[] ParseMap) { lock (SyncLock) { if (ParserCache.ContainsKey(ReturnType)) return ParserCache[ReturnType]; } IParser obj = GetParser(ReturnType, ParseMap); lock (SyncLock) { if (ParserCache.ContainsKey(ReturnType)) return ParserCache[ReturnType]; ParserCache.Add(ReturnType, obj); return obj; } } public static IParser GetParser(Type ReturnType, string[] ParseMap) { CodeCompileUnit targetUnit; CodeTypeDeclaration targetClass; targetUnit = new CodeCompileUnit(); CodeNamespace samples = new CodeNamespace(typeof(DataParserGenerator).Namespace.ToString()); samples.Imports.Add(new CodeNamespaceImport("System")); targetClass = new CodeTypeDeclaration("Parser_Generated_" + ReturnType.Name); targetClass.IsClass = true; targetClass.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed; targetClass.BaseTypes.Add(new CodeTypeReference(typeof (IParser))); samples.Types.Add(targetClass); targetUnit.Namespaces.Add(samples); AddParseMethod(targetClass, ReturnType, ParseMap); IParser obj = (IParser) ReturnCompiledClass(targetUnit, samples.Name + "." + targetClass.Name); return obj; } private static void AddParseMethod(CodeTypeDeclaration targetClass, Type ReturnType, string[] ParseMap) { CodeMemberMethod method = new CodeMemberMethod(); method.Name = "Parse"; method.Attributes = MemberAttributes.Public | MemberAttributes.Final; method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object[]), "param")); method.ReturnType = new CodeTypeReference(ReturnType); method.Statements.Add( new CodeVariableDeclarationStatement(ReturnType, "_data", new CodeObjectCreateExpression(ReturnType))); for (int i = 0; i < ParseMap.Length; i++) { PropertyInfo prop; //Only support properties for now. prop = ReturnType.GetProperty(ParseMap[i]); if (prop == null) continue; CodeArrayIndexerExpression mapIndexer = new CodeArrayIndexerExpression( new CodeVariableReferenceExpression("param"), new CodePrimitiveExpression(i)); //If System.Convert has a conversion method, use it. Otherwise, just cast if (typeof(System.Convert).GetMethod("To" + prop.PropertyType.Name, new Type[] { typeof(object) }) != null) { CodeTypeReferenceExpression ctr = new CodeTypeReferenceExpression(typeof(System.Convert)); CodeMethodInvokeExpression cme = new CodeMethodInvokeExpression(); cme.Method = new CodeMethodReferenceExpression(ctr, "To" + ReturnType.GetProperty(ParseMap[i]).PropertyType.Name); cme.Parameters.Add(mapIndexer); method.Statements.Add(new CodeAssignStatement( new CodeVariableReferenceExpression("_data." + ParseMap[i]), cme )); } else { method.Statements.Add(new CodeAssignStatement( new CodeVariableReferenceExpression("_data." + ParseMap[i]), new CodeCastExpression(prop.PropertyType, mapIndexer))); } } method.Statements.Add(new CodeMethodReturnStatement(new CodeArgumentReferenceExpression("_data"))); targetClass.Members.Add(method); } private static object ReturnCompiledClass(CodeCompileUnit targetUnit, string targetClassName) { CSharpCodeProvider provider = new CSharpCodeProvider(); // Build the parameters for source compilation. CompilerParameters cp = new CompilerParameters(); // Generate a class library. cp.GenerateExecutable = false; // Do not save the assembly as a physical file. cp.GenerateInMemory = true; // Need to referenc current assembly because it containts definitions of IParse and DataClass cp.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); #if DEBUG StringBuilder sb = new StringBuilder(); StringWriter sw = new StringWriter(sb); provider.GenerateCodeFromCompileUnit(targetUnit, sw, null); sw.Close(); System.Diagnostics.Debug.WriteLine(sb.ToString()); #endif CompilerResults cr = provider.CompileAssemblyFromDom(cp, targetUnit); if (cr.Errors.Count == 0) { return cr.CompiledAssembly.CreateInstance(targetClassName); } else return null; } } }

Here's a sample way of calling the parser:

IParser dcParser; string[] s ={"1", "2", "3", "4"}; string[] ParseMap = new string[] { "FirstInt", "FirstString", "SecondInt", "SecondString" }; dcParser= DataParserGenerator.GetParser(typeof (DataClass), ParseMap); DataClass dc = dcParser.Parse(s);

And Here's the generated code (from the debug output window)

namespace CodeDomTest { using System; public sealed class Parser_Generated_DataClass : CodeDomTest.IParser { public CodeDomTest.DataClass Parse(object[] param) { CodeDomTest.DataClass _data = new CodeDomTest.DataClass(); _data.FirstInt = System.Convert.ToInt32(param[0]); _data.FirstString = System.Convert.ToString(param[1]); _data.SecondInt = System.Convert.ToInt64(param[2]); _data.SecondString = System.Convert.ToString(param[3]); return _data; } } }

That's all... I told you it's easy.