Setting up a Continuous Integration System, Part 6: CruiseControl.NET Custom Plug-in: Source Retrieval

CruiseControl.NET has an extensible plug-in architecture. While documentation on how to take advantage of it is sparse, there are some sources I’ve found helpful: Custom Builder Plug-in, which is a tutorial on how to write a plug-in derived from ITask, and the TFS Plug-in for CruiseControl.NET project on CodePlex, which is a great code resource for writing a plug-in derived from ISourceControl.

In this post I’ll discuss two custom plug-ins, both derived from ISourceControl, whose shared purpose is to retrieve source code prior to a build. The only difference is that one interfaces with Subversion while the other, Team Foundation Server. While there are obviously coding differences because of this, the general workflow of each plug-in is the same.

CruiseControl.NET Assembly References

There are minimally two dll’s you will need to add as references to any CC.NET plug-in project: ThoughtWorks.CruiseControl.Core.dll and NetReflector.dll. The Core dll contains the interfaces and other goodness; NetReflector contains the attributes you’ll need to mark your classes, methods, etc. so CC.NET knows what to do with it. Once you’ve installed CruiseControl.NET, you’ll find both of these assembles in the C:Program FilesCruiseControl.NETserver folder.

Writing a CC.NET Custom ISourceControl Plug-in

Writing an ISourceControl plug-in is very similar to creating an ITask plug-in. Rather than duplicate work by writing up on my own tutorial, I’ll instead refer you to the Custom Builder Plug-in, which takes you step-by-step on how to write an ITask plug-in. Also, refer to the TFS Plug-in for CruiseControl.NET project as that contains a lot of good information.

A Word About CI Source Control Tasks

Retrieving source is the first and one of the most fundamental steps in any CI workflowprocess:

image

If source has not changed, CC.NET pauses and checks again later. If source has changed, it will do a ‘get’ and proceed with a new build. The ‘get’ part is where our ISourceControl-derived plug-in comes in.

Before I jump into the plug-ins, a quick word about how I solved the “update version never-ending loop” problem: as described in Part 5 of this series, my Update Version task updates each assembly’s AssemblyFileVersion attribute in each of the AssemblyInfo.cs files before checking that file into source control. The next time CC.NET runs, it sees that change, picks it up, and does another build. This creates a looping situation since, once again, the version is updated and checked in so that the next time CC.NET checks for new source… I’m sure you can see where this is going. The way I solve this is to have the Update Version task add a special marker into the source file’s comment on check-in. When the Get Source plug-in sees that marker, it ignores the file. If that’s the only change, it goes back to sleep. No looping. Problem solved.

Get Source Plug-in for Subversion

I’ll start with a class derived from ISourceControl:

   1: namespace ccnet.svnget.plugin
   2: {
   3:     [ReflectorType ("svnget")]
   4:     public class SVNGet : ISourceControl
   5:     {
View Plain

Deriving from ISourceControl requires us to implement a handful of methods. We’ll get to those in a minute. For now, note the ReflectorType attribute, which names the task for use by CC.NET.

Like the Update Version plug-in from the previous post, I define the special marker which this plug-in will be looking for in the source control comment:

   1: private const string _marker = "***NO_CI***";    // if present in comment, ignore file
View Plain

A few properties:

   1: /// <summary>
   2: /// Subversion username, can be set in TortoiseSVN settings if installed.
   3: /// </summary>
   4: [ReflectorProperty ("username", Required = false)]
   5: public string Username { get; set; }
   6:  
   7: /// <summary>
   8: /// Subversion password, can be set in TortoiseSVN settings if installed.
   9: /// </summary>
  10: [ReflectorProperty ("password", Required = false)]
  11: public string Password { get; set; }
  12:  
  13: /// <summary>
  14: /// The local working folder where Subversion commands will be run.
  15: /// </summary>
  16: [ReflectorProperty ("workingDirectory")]
  17: public string WorkingDirectory;
  18:  
  19: /// <summary>
  20: /// The SVN executable, including path if necessary.
  21: /// </summary>
  22: [ReflectorProperty ("executable")]
  23: public string Executable;
View Plain

Similar to how the ReflectorType attribute defines the CC.NET task, ReflectorProperty defines properties for the task. Note that if you are using Visual SVN/TortoiseSVN, you can set your credentials through those applications and do not have to add them as properties in your ccnet.config file.

Now, there are a number of methods you must implement when deriving from ISourceControl. Here’s a few I didn’t do much with:

   1: public void LabelSourceControl (IIntegrationResult result)
   2: {
   3:     return;
   4: }
   5:  
   6: public void Initialize (IProject project)
   7: {
   8:     return;
   9: }
  10:  
  11: public void Purge (IProject project)
  12: {
  13:     return;
  14: }
View Plain

Let’s move on to something more interesting…

GetModifications is an ISourceControl method we implement to retrieve a list of modified files:

   1: /// <summary>
   2: /// Retrieves a list of modified files from Subversion.
   3: /// </summary>
   4: /// <param name="from"></param>
   5: /// <param name="to"></param>
   6: /// <returns>A list of files modified in source control.</returns>
   7: public Modification[] GetModifications (IIntegrationResult from, IIntegrationResult to)
   8: {
   9:     var modifications = new List<Modification> ();
  10:  
  11:     if (!GetModifiedFiles (ref modifications))
  12:     {
  13:         Log.Info ("Failed checking for source modifications.");
  14:     }
  15:  
  16:     return (modifications.ToArray ());
  17: }
View Plain

All of the works happens in GetModifiedFiles, which is jam-packed with action. It’s a big method, too. I’ll go over it in pieces.

The first thing I do is run an SVN status command using our RunProcess helper (see source code download below for more info on how RunProcess works), using the –u flag to retrieve file status for those files that have changed in the repository:

   1: argBuilder = new ProcessArgumentBuilder ();
   2: argBuilder.AppendArgument ("status -u");        // -u will give us an '*' for those files that have changed on the server
   3: argBuilder.AppendArgument (WorkingDirectory);
   4: AppendCommonSwitches (argBuilder);
   5:  
   6: var result = RunProcess (argBuilder);
View Plain

What this does is give us a list of potentially changed files which we’ll then iterate through (this is the first part of the iteration loop):

   1: var potentiallyModifiedItems = result.StandardOutput.Split (newline);
   2: foreach (var svnItem in potentiallyModifiedItems)
   3: {
   4:     // the standard output contains double end of line's, so we'll wind up with some blank entries here
   5:     if (string.IsNullOrEmpty (svnItem)) continue;
   6:  
   7:     if (svnItem[0] == '?') continue;    // item not under source control
   8:  
   9:     // 'M' indicates modified item
  10:     if (svnItem[8] != '*') continue;    // no '*' = file has not changed on server
  11:  
  12:     var file = svnItem.Substring (21);    // full path + file (if not a directory) starts at the 21st 0-indexed position
  13:  
  14:     // need to examine log for our marker, which if found will tell us to ignore this changed file
  15:     argBuilder = new ProcessArgumentBuilder ();
  16:     argBuilder.AppendArgument ("log -r HEAD");    // get the latest (i.e., HEAD) log info
  17:     argBuilder.AppendArgument (file);
  18:     AppendCommonSwitches (argBuilder);
  19:  
  20:     result = RunProcess (argBuilder);
View Plain

We filter out some of the results, then, once we have a candidate file, we examine the source comment for our special marker. If the marker is found, we ignore the file, otherwise we process it:

   1: if (!result.StandardOutput.Contains (_marker))
   2: {
   3:     // modified file has not been "marked", so add to modifications list
   4:  
   5:     // example log output (the whole thing is contained within 'infoLines'):
   6:     //
   7:     // ------------------------------------------------------------------------
   8:     // r309 | scottfm | 2009-08-28 20:57:18 -0500 (Fri, 28 Aug 2009) | 1 line        <-- Line 1; split into modificationAttributes
   9:     //
  10:     // ***NO_CI*** AssemblyFileVersion updated from version [1.0.1.8] to [1.0.1.9].    <-- Line 3
  11:     // ------------------------------------------------------------------------
  12:  
  13:     var infoLines = result.StandardOutput.Split (newline);
  14:     var infoLinesList = new List<string>();
  15:  
  16:     // a lot of massaging of stdout output... see download for full source
  17:  
  18:     // version info
  19:     var versionString = modificationAttributes[0].Substring (1);
  20:     var version = Convert.ToInt32 (versionString);
  21:  
  22:     // modified date/time
  23:     var findFirstSpace = modificationAttributes[2].IndexOf (' ');
  24:     var findSecondSpace = modificationAttributes[2].IndexOf (' ', findFirstSpace + 1);
  25:     var datetime = modificationAttributes[2].Substring (0, findSecondSpace);
  26:     var modifiedDateTime = DateTime.Parse (datetime);
  27:  
  28:     var modification = new Modification
  29:                            {
  30:                                UserName = modificationAttributes[1],
  31:                                Comment = infoLines[3],
  32:                                ChangeNumber = version,
  33:                                ModifiedTime = modifiedDateTime,
  34:                                Version = versionString,
  35:                                Type = string.Empty,
  36:                                FileName = Path.GetFileName (file),
  37:                                FolderName = Path.GetDirectoryName (file)
  38:                            };
  39:  
  40:     modifiedFilesList.Add (modification);
View Plain

There’s a lot of massaging of the stdout output; I left that out of the above snippet so we can get to the good stuff, which is to build up a Modification object and add it to our modifiedFilesList collection. Since the collection was passed in using the ‘ref’ keyword, the list we build here is passed back by reference to GetModifications whereupon it is returned to CC.NET.

The last part of this plug-in is to actually get the source from SVN so CC.NET can build it. We do that with the ISourceControl method, GetSource (It’s of some confusion when coming from the Visual SourceSafe or TFS world that the equivalent of a ‘get’ is actually termed an ‘update’ in SVN). In GetSource, we’ll perform an SVN update in order to retrieve files that been modified and checked-in to source control:

   1: /// <summary>
   2: /// Retrieves changed files from source control.
   3: /// </summary>
   4: /// <param name="result"></param>
   5: public void GetSource (IIntegrationResult result)
   6: {
   7:     // we'll run an svn update to retrieve modified files
   8:  
   9:     var argBuilder = new ProcessArgumentBuilder ();
  10:     argBuilder.AppendArgument ("update");
  11:     argBuilder.AppendArgument (WorkingDirectory);
  12:     AppendCommonSwitches (argBuilder);
  13:  
  14:     var runProcessResult = RunProcess (argBuilder);
  15:  
  16:     Debug.WriteLine (string.Format ("SVN update output [{0}]", runProcessResult.StandardOutput));
  17:     Log.Debug (string.Format ("SVN update output [{0}]", runProcessResult.StandardOutput));
  18:  
  19:     return;
  20: }
View Plain

RunProcess runs an SVN update on our working directory. This has the effect of retrieving any source that has been modified since the last time we did an update. With that, we’re ready for CC.NET to perform our build.

For reference, here’s the task as defined in the ccnet.config file:

   1: <sourcecontrol type="svnget">
   2:     <executable>c:program filesVisualSVNbinsvn.exe</executable>
   3:     <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
   4: </sourcecontrol>
View Plain

I’ll be going over CC.NET’s ccnet.config file in Part 8 of this series.

That’s it for the Get Source SVN plug-in.

Get Source Plug-in for TFS

I won’t go into a lot of detail on the Get Source Plug-in for TFS as it is very similar to the Update Version Plug-in for TFS discussed in my last post. Besides for that, I didn’t write this plug-in. Credit Martin Woodward with that Herculean feat. I did, however, make one small change to his code to handle my special marker scenario.

That code is:

   1: // 2009-05-01 - sfm - ignore code change if comment contains "***NO_CI***"; this will prevent recursive builds on version updating
   2: Log.Debug (string.Format ("Checking for NO_CI flag for project [{0}]...", from.ProjectName));
   3: if (comment.Contains ("***NO_CI***"))
   4: {
   5:     Log.Info ("Ignoring file");
   6:     continue;
   7: }
View Plain

Which quite simply looks for the marker. If it finds it, the file is ignored inasmuch as the change set is concerned.

For reference, here is how one might define this task in CC.NET’s ccnet.config file:

   1: <sourcecontrol type="vsts" autoGetSource="true" applyLabel="false">
   2:     <server>http://tfs-server/</server>
   3:     <project>$/MyProject</project>
   4:     <workingDirectory>c:MyProject</workingDirectory>
   5:     <workspace>CCNET</workspace>
   6: </sourcecontrol>
View Plain

Conclusion

This plug-in stuff is exhausting! But we’re done now. Hopefully between this post and the last one you have enough information to write just about any CruiseControl.NET plug-in you might ever need.

Note that the download contains my modified version of Martin Woodward’s TFS Plug-in for CruiseControl.NET. I can’t guarantee I have the latest and greatest source for that, so visit the CodePlex site if you want to start with his newest code.

Also, the CCNETPlugins download contains both the Get Source and Update Version plug-ins.

Download: CCNETPlugins.zip

Resources

NetReflector: One Minute Introduction

Custom Builder Plug-in

TFS Plug-in for CruiseControl.NET

VSTSPlugins

How to install Visual SVN

July 2, 2009 12:26

Introduction

I’m a long time user of—cough, cough—Visual SourceSafe, both at work and home. At home it’s done pretty well. At work, with dozens of users, not so much. We’re solving the problem at work by migrating to Microsoft Team Foundation Server. This has prompted me to think about my home/personal solution for source control. While I could continue to use VSS, I thought it was time to take a look at Subversion, which I’d heard a lot of good things about. Subversion is free to inexpensive, it’s open source, and there are a wide variety of implementations and tools from which to choose. Factor in VSS’s most debilitating feature, or lack thereof, that being remote access, and moving to the right Subversion package was a no-brainer.

So, what exactly is Subversion? From the Subversion project page:

Subversion is an open source version control system.

Simple as that, if only there weren’t so many implementations. Fortunately, I’d done some prior research about a year ago when the idea of moving away from VSS started nagging at me, so I already had an idea which package I wanted to use. As if you couldn’t tell from the title of this post, that implementation is Visual SVN. In this post, I’ll take you through the installation of both the server and client pieces. Note that while the server piece is free, the client costs $49. You can get by with just Tortoise SVN (which is free) on the client-side, but I wanted a solution that integrates with Visual Studio.

Why Visual SVN?

First, why Visual SVN?

  1. Regarded as one of the better SVN implementations
  2. Integrates with Visual Studio 2008 (this is a must-have requirement)
  3. Server is accessible remotely over http/https (this is a must-have requirement)
  4. While not free, the cost of the client is minimal (the server is free)

What You Need to Get Started

There are three downloads you’ll need:

1.) Visual SVN Server (cost: free)

2.) Visual SVN Client (cost: $49; you can download, install, and try it out during the trial period)

3.) Tortoise SVN (cost: free; Visual SVN recommends installing Tortoise SVN as it utilizes Tortoise for much of the file system/Windows explorer integration)

Get those now.

Installing the Visual SVN Server

Installing the server is pretty straightforward. I’m going to install on my home Windows 2003 server. You can probably install on your local machine, but I wouldn’t recommend it. It’s better to have your source control repository on a separate box for safety if nothing else. I do have the added bonus of being able to access my home server from any location (including SVN once it’s installed), so I definitely want my repository there.

1.) First, locate the Visual SVN Server msi and double-click it. Click-through any pesky security warning dialogs until you see:

image

2.) Click ‘Next’. You’ll see:

image

3.) Accept the licensing terms and click ‘Next’. Now we get to the meat of the install:

image

I was happy with all the defaults but for the Authentication selection. I chose to select “Use Windows Authentication” because my Windows 2003 server already acts as a domain controller, so I figure why not use my existing credentials?

A brief breakdown of the other items:

  • Install Location is what it is; change if you like.
  • Repository, again, is up to you. I left it alone.
  • Server Port is the port Visual SVN Server will listen on for incoming connections.
  • I chose to keep Use secure connection checked. I’m not that concerned with security (most of my personal/home projects are available via this blog, anyway), but I figure if it’s there and it works, why not?
  • As above, I selected “Use Windows Authentication” as my Authentication scheme.

When you’re happy with your selections, click ‘Next”.

5.) Almost done. Click ‘Install’.

image

Wait while Visual SVN Server installs.

6.) Click ‘Finish’ to launch the Visual SVN Manager.

image

7.) With the Manager running, you’ll see the following dialog. Visual SVN Server runs as a Windows service, so the Manager is really just the management interface, and doesn’t need to run in order for you to access your source control repository.

image

8.) Let’s add a user. Right-click on ‘Repository’, select ‘All Tasks’, ‘Manage Security…’:

image

There’s a generic group already there. I removed it, and added my own domain login explicitly. By default, you’ll have read/write access.

image

That’s it for the server install. Let’s install the client.

Installing the Visual SVN Client

Now, let’s go through the steps to install the Visual SVN client on your development/client machine.

1.) Locate the Visual SVN client msi and double-click to install. Click-through security warnings until you see:

image

2.) Click ‘Next’ and you’ll see the following dialog. Accept the License Agreement and click ‘Next’ again.

image

3.) Now you’ll see this dialog:

image

I chose to not install the “Visual Studio 2003 Integration” piece as I do not develop in VS2003 anymore. I don’t use VS2005 much either, but I figured I’d leave it just in case.

4.) Click ‘Next’ to get here:

image

Click ‘Install’ to install the Visual SVN client.

5.) One last dialog. If you haven’t already, you can click ‘Download Now’ to download Tortoise SVN. Tortoise provides a lot of the file-level plumbing for Visual SVN, and is a recommended (and free, though donations are accepted), complementary install. Next, we’ll go over installing Tortoise SVN.

image

Installing Tortoise SVN

1.) Locate the Tortoise SVN msi and double-click to install. Click-through the usual security warnings and you’ll see:

image

2.) Click ‘Next’, accept the licensing terms on the next dialog, and click ‘Next’ again.

image

3.) On the following dialog I didn’t see a need to install the ‘GB’ version of English, but I went with everything else.

image

4.) Click ‘Next’, then ‘Install’ to get the install rolling.

image

5.) You’ll notice the ‘Donate’ button on the install dialog. If you feel so inclined…

image

6.) That’s it. Pretty painless. Click ‘Finish’ to exit the final dialog.

image

7.) Oops. One more step. You may want to restart as it asks. I did.

image

Conclusion

To recap, my primary requirements for moving to any Subversion-based source control solution included:

  1. Must integrate with Visual Studio
  2. Must allow remote access via http/https

As far as #1, you can see below that Visual SVN gives you a new menu in Visual Studio from which you can browse your repositories and manage your source control functions.

image

For remote access, the Repository Browser allows me to access my server’s repository:image

Of course, remember to open port 8443 (or whatever port you specified during install) on your firewall.

SQL Server 2008 Express Silent Command-Line Install

I have a need to perform a silent, command-line install of SQL Server 2008 Express as a pre-requisite for a server application. While there’s an extensive listing of all the many options available for a command-line install of SQL Server, it still took me a while to sift through what was really needed for a basic, minimal install.

I should add that I’m using InstallShield and setting SQL Server as one of the pre-req’s, but it still requires the command-line switches:

image

What you’re seeing up there is the right way to do it. Before, I only had the “/q” switch, telling SQL Server to run silently. I thought that was enough. Oh, was I wrong. I ran the install, SQL Server looked like it was doing something, but I kept getting an error dialog from InstallShield saying the SQL Server part of the install did not complete successfully. So, the first thing I did was take a step back and attempt to get just the SQL Server install to run, sans InstallShield, from a plain old command prompt.

The first step was to, of course, get SQL Server Express (I was letting InstallShield download from the web, and who knows what happened to that copy). You’ve got several choices; I only wanted the basic database, so I chose the “Database Only” option.

image

Note that if you’re using a plain vanilla VPC like me with a new Windows OS install, you’ll likely also need to install Windows Installer 4.5, which is required by SQL Server.

As you’re trying to get your command-line setup correctly, one of the best places I found to determine what went wrong when an install didn’t work was to look at the log file in the folder “C:Program FilesMicrosoft SQL Server100Setup BootstrapLog”. Open one of the sub-folders (which are named with the install time) and open the “Summary” text file.

With Windows Installer and SQL Server Express 2008 installed, here’s the command-line I wound up using:

   1: SQLEXPR32_x86_ENU.exe /q /ACTION=Install /IACCEPTSQLSERVERLICENSETERMS
   2:    /INSTANCENAME=SQLSERVER /ROLE=AllFeatures_WithDefaults
   3:    /ADDCURRENTUSERASSQLADMIN=TRUE /SQLSVCACCOUNT="NT AUTHORITYNetwork Service"
   4:    /FEATURES=SQL
 I think it’s about as minimalistic as you can get. I’m not so sure about running the sqlserver.exe service under the NETWORK SERVICE account, but that’s what I’m going with for the time being. I’ll update if I find a recommendation on this, or please leave a comment below if you’ve got something on this.

Oddly enough, you can’t just take those switches and throw them into InstallShield. You’ll get a couple of errors during the SQL Server install if you do (take a look at the SQL log files at “C:Program FilesMicrosoft SQL Server100Setup BootstrapLog” if you do). So, here are the switches I had to use to keep InstallShield happy:

   1: /q /ACTION=Install /BROWSERSVCSTARTUPTYPE=Automatic /SQLSVCSTARTUPTYPE=Automatic
   2:    /FEATURES=SQL /INSTANCENAME=SQLEXPRESS /SQLSVCACCOUNT="NT AUTHORITYNetwork Service"
   3:    /SQLSYSADMINACCOUNTS="BUILTINADMINISTRATORS" /AGTSVCACCOUNT="NT AUTHORITYNetwor

URL Regular Expression Validation

I was adding some feeds to my blogroll recently, not really thinking about what I was doing, until I went to my blog’s home page and got this:

clip_image002

Luckily I was doing periodic refreshes, just to see how the list was coming along, so it was easy to identify the culprit:

feed://http//underground.infovark.com/feed/

Look a little strange?

Here’s the offending code from “App_CodeControlsBlogroll.cs”, which throws the exception when attempting to display the feeds in my blogroll:

 Line 211:
 Line 212:      item.Request = (HttpWebRequest)WebRequest.Create(feedUrl);
 Line 213:      item.Request.Credentials = CredentialCache.DefaultNetworkCredentials;
View Plain

Specifically, line 212 tries to do a Create on the bad URL and hits the NotSupportedException.

Fortunately, this sort of behavior can easily be stopped before it becomes an issue by fixing the problem at the source. We’re going to add a couple of regular expression validators at the point at which we add the feeds, thereby preventing the error condition from arising in the first place. Of course, even with this, you’ll want to go into the Blogroll.cs control and wrap the “Create” in a try/catch. It’s just good programming sense.

If you’re using BlogEngine.NET, this step-by-step should be pretty straightforward:

1.) Open up “adminPagesBlogroll.aspx”

We’re going to modify the aspx file instead of the code-behind. You really can go either way, but this seemed the cleaner approach.

2.) Add two “RegularExpressionValidator” controls

There are two input fields we are concerned with: the Web URL text box and the RSS URL text box. First, comment out both “RequiredFieldValidator” controls. We don’t need those. Then, add two RegularExpressionValidator’s so your code looks like:

   1: <label for="<%=txtWebUrl.ClientID %>" class="wide"><%=Resources.labels.website %></label>
   2: <asp:TextBox runat="server" ID="txtWebUrl" Width="600px" />
   3: <%--<asp:RequiredFieldValidator runat="Server" ControlToValidate="txtWebUrl" ErrorMessage="required" /><br />--%>
   4: <asp:RegularExpressionValidator runat="Server" ControlToValidate="txtWebUrl" ErrorMessage="Invalid URL" ValidationExpression="[a-zA-Z0-9-.]+.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9-._?,'/\+&%$#=~])*" /><br />
   5:   
   6: <label for="<%=txtFeedUrl.ClientID %>" class="wide">RSS url</label>
   7: <asp:TextBox runat="server" ID="txtFeedUrl" Width="600px" />
   8: <%--<asp:RequiredFieldValidator runat="Server" ControlToValidate="txtFeedUrl" ErrorMessage="required" /><br />--%>
   9: <asp:RegularExpressionValidator runat="Server" ControlToValidate="txtFeedUrl" ErrorMessage="Invalid URL" ValidationExpression="[a-zA-Z0-9-.]+.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9-._?,'/\+&%$#=~])*" /><br />
View Plain

The important properties include “ControlToValidate”, which ties the validator to the text box:

   1: ControlToValidate="txtFeedUrl"
View Plain

and “ValidationExpression”, which contains our regular expression:

   1: ValidationExpression="[a-zA-Z0-9-.]+.[a-zA-Z]{2,3}(:[a-zA-Z0-9]*)?/?([a-zA-Z0-9-._?,'/\+&%$#=~])*"
The regular expression looks pretty cryptic (they all do), but it should provide proof against such malformed URL’s as the one I started this post with.

To test it out, let’s give the new validator a try. Here’s what I get when I try to add a new blogroll using the “bad” URL:

image

Looks good, and no more yellow screen of death.

Build up Your Online Business like an Expert

There can be a lot of complex ideals when it comes to creating an online business, like https://www.emethod.ca/vancouver-seo-services/, website development or even webpage traffic indicators, all of which can seem very intimidating when you are just about to begin your opportunity in creating your very own online company. Luckily for you with so many different tools available online it is not that difficult nowadays to come up with a brand that you can build up from scratch, as long as you get to acknowledge some of these useful hints and easy to follow guidelines, you are sure to be on your way to making your business like an expert.

 

Ready-made Pages and Sites

The amount of information that is available online is numerous and the great thing about it is there are a lot of pages that cater to those who are looking to begin their journey online of having a webpage, and just from here you will be able to recognize that you have an upper hand when you want to startup your brand and share it online. Utilize these pages and find those that are of high quality and are really created to provide the necessary tools for those who want to create their own online business, in this way it becomes a lot easier for you to build on all of your ideas.

 

Utilize Free Tools Online

With all the different sites that have come into popularity these days all those that are free of use should be of big use for you in terms of marketing your own brand especially since there are a lot of popular companies that have been able to turn these online tools into a great way to pool out some best deals and offers. Some of the most used tools and sites are those in the track of social media which really creates a big opportunity for different brands to be well known among a big percentage of the online market of population across the globe.

 

Create Within Preferences

Of course, your own brand should be well represented with your own scope of ideals and personal preferences especially when these are services and products that you offer at a very unique variety and is one of a kind compared to so many other brands out there. This should also be the same ideal that you put out when it comes to your web pages, as these should all be able to represent your brand to be unique and one of a kind, one that would be able to reach out to your potential consumers and make them come back and continue their support for your brand.

 

Market Research and Reviews

Being an expert in creating your own business pages online will also bring you to the task of being very familiar with your own market and seeing where it is taking you in terms of what changes may need to be done with your promotions as well as if the services and products you provide need any changes. Being on top of the game will require you not only to recognize what your consumers clamor for but it should also lead you to see where you are in terms of your market competitors.

How slow is .NET Reflection?

I’ve run into a situation where I’d like to use a small bit of Reflection. While this particular use that I have in mind is pretty simple, the method to which I will be adding the Reflection can potentially be called tens of thousands of times per day (if not more), so it has to be fast. Reflection has a reputation as anything but, so I’d like to see just how slow is Reflection?

Measuring Slowness

I’m going to measure efficiency (or slowness) using the actual code I intend to use in my application sandwiched between time snapshot functionality provided by .NET’s StopWatch object. For capturing timing data, DateTime is another possibility, but, as I quickly discovered, DateTime is not efficient enough to capture the time snapshot data I needed.

I’ll take measurements for different scenarios: single call, looped a hundred times, a thousand, etc. to gather total time elapsed for each.

The time recording code will look like:

   1: Stopwatch Elapsed = new Stopwatch();
   2:  
   3: for (var i = 0; i < nIterations; i++)
   4: {
   5:     Elapsed.Start();
   6:     // do something
   7:     Elapsed.Stop();
   8: }
View Plain

Pretty straightforward. Record start time. Do something. Get stop time. You get Ticks and Milliseconds from StopWatch’s ElapsedTicks and ElapsedMilliseconds properties.

All test results will come from Release builds of the sample application. Test results will be dumped to a text file. Full sample code and VS2008 project are attached below.

The Reflection Code

As mentioned above, the Reflection code I want to use is very simple:

   1: string strThisMethodName = MethodBase.GetCurrentMethod ().Name;
View Plain

It just gets the name of the currently executing method. Real-world use then uses that method name as part of a string that gets recorded to a logging system.

For purposes of testing, I’m going to leave out the assembly of the string and the subsequent logging. Building the string is going to happen with or without the Reflection piece. So is the logging. I’ll therefore exclude them from the testing.

Testing

Here is the full method that will do the testing, including the single line of Reflection code whose performance I want to measure:

   1: static private void RunWithIterations (int nIterations)
   2: {
   3:     Debug.WriteLine ("");
   4:     Debug.WriteLine ("Running with " + nIterations + " iterations...");
   5:  
   6:     sw.WriteLine ("");
   7:     sw.WriteLine ("Running with " + nIterations + " iterations...");
   8:  
   9:     Stopwatch Elapsed = new Stopwatch();
  10:  
  11:     for (var i = 0; i < nIterations; i++)
  12:     {
  13:         Elapsed.Start();
  14:         string strThisMethodName = MethodBase.GetCurrentMethod ().Name;
  15:         Elapsed.Stop();
  16:     }
  17:  
  18:     Debug.WriteLine ("Total elapsed time (ticks) [" + Elapsed.ElapsedTicks + "]");
  19:     Debug.WriteLine ("Total elapsed time (ms) [" + Elapsed.ElapsedMilliseconds + "]");
  20:  
  21:     sw.WriteLine ("Total elapsed time (ticks) [" + Elapsed.ElapsedTicks + "]");
  22:     sw.WriteLine ("Total elapsed time (ms) [" + Elapsed.ElapsedMilliseconds + "]");
  23: }
View Plain

You can vary the iterations by use of the nIterations parameter. This allows for single or multiple test scenarios.

Results

If I run the above code with nIterations = 1, I get:

   1: Running with 1 iterations...
   2: Total elapsed time (ticks) [2393]
   3: Total elapsed time (ms) [0]
View Plain

Not bad. Let’s try 100:

   1: Running with 100 iterations...
   2: Total elapsed time (ticks) [4980]
   3: Total elapsed time (ms) [0]
View Plain

Now, let’s go ahead and run the full spread: 1 iteration all the way to 1 million. Here are the results:

Running with 1 iterations…
Total elapsed time (ticks) [2393]
Total elapsed time (ms) [0]

Running with 100 iterations…
Total elapsed time (ticks) [4980]
Total elapsed time (ms) [0]

Running with 1000 iterations…
Total elapsed time (ticks) [48869]
Total elapsed time (ms) [3]

Running with 10000 iterations…
Total elapsed time (ticks) [529298]
Total elapsed time (ms) [36]

Running with 100000 iterations…
Total elapsed time (ticks) [5297636]
Total elapsed time (ms) [369]

Running with 1000000 iterations…
Total elapsed time (ticks) [51573509]
Total elapsed time (ms) [3601]

My overall opinion is ‘not too bad’. Even at 1,000,000 calls, efficiency is not impacted. Also, given that the Reflection operation is just a small part of the overall operation the real-world application performs, the time increase is insignificant.

Reflection Caching

Turns out Reflection has a caching mechanism:

[…] reflection and the runtime have a number of caches in place to reduce the number of times the reflection runtime has to travel to metadata to pick up the bits.

Even better. There may be a small performance hit the first time Reflection has to hit the metadata, but after that you’re all cache and response time should be very fast.

Coding Standards & Conventions

This is my attempt to list those resources having to do with coding standards that I’ve found useful.

If you have your own favorites, let me know and I’ll add them or just link them in the comments.

Here’s the list.

.NET/C#

  • MSDN: Design Guidelines for Class Library Developers
  • Brad Abrams’ Framework Design Guidelines
  • .NET Framework Developer’s Guide: Guidelines for Names
  • irritatedVowel.com: .NET Programming Standards and Naming Conventions
  • Phillips Electronics C# Coding Standard (.pdf)
  • IDesign C# Coding Standard (.zip)
  • Lance’s Whiteboard: C# Coding Standards
  • Custom Control Development: Simple code guidelines

How to build a JavaScript to Silverlight Bridge

Recently, I noticed a problem with my other blog where the live comment preview was not working. Chances are it broke when I upgraded BlogEngine.NET. I played around with the AJAX/JavaScript a bit, couldn’t get it to work, then had a thought: why not implement the same functionality in Silverlight?

The basic idea is to have two TextBox controls, one defined in ASP.NET and the other in the Silverlight control. The former will accept user input. The latter will spit it back out (but with formatting tags specified by the user applied to the text; that functionality will be part of my next post on this project).

But before I jump into the control itself I needed to know how to pass user input from the ASP.NET control to the Silverlight control. Turns out you can do that with a JavaScript to Silverlight bridge

Create a New Silverlight Project

To start, you’ll need a basic Silverlight project (or, alternatively, you could just download the source linked at the bottom of this post).

1.) Create a new Silverlight project. I called mine “JStoSLBridge”.

image

Since we need an ASP.NET page to go along with this project, we’ll add a new ASP.NET Web project:

image

2.) As a further setup step, add the Silverlight project as a Silverlight Application in the web application.

image

You can leave the default settings and click “Add”.

image

The ASP.NET/JavaScript Section of the Bridge

Let’s get started with demonstrating how to pass data from ASP.NET into a JavaScript function and then make the jump from there into our Silverlight application.

1.) This is what we have by default in the JStoSLBridgeTestPage.aspx page:

   1: <%@ Page Language="c#" AutoEventWireup="true" %>
   2:  
   3: <%@ Register Assembly="System.Web.Silverlight" Namespace="System.Web.UI.SilverlightControls"
   4:     TagPrefix="asp" %>
   5:  
   6: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   7:  
   8: <html xmlns="http://www.w3.org/1999/xhtml" style="height:100%;">
   9: <head runat="server">
  10:     <title>JStoSLBridge</title>
  11: </head>
  12: <body style="height:100%;margin:0;">
  13:     <form id="form1" runat="server" style="height:100%;">
  14:         <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
  15:         <div  style="height:100%;">
  16:             <asp:Silverlight ID="mySLApp" runat="server" Source="~/ClientBin/JStoSLBridge.xap" MinimumVersion="2.0.31005.0" Width="100%" Height="100%" />
  17:         </div>
  18:     </form>
  19: </body>
  20: </html>
View Plain

We’ll make some additions, including adding a TextBox to take user input and the JavaScript function which is the first part of our bridge.

Here’s the ASP.NET code:

   1: <form id="form1" runat="server" style="height:100%;">
   2:     <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
   3:     <asp:TextBox ID="TextBox1" runat="server" onkeyup="UpdateLivePreview()"></asp:TextBox>
   4:     <br /><br />
   5:     <div style="height:100%;">
   6:         <asp:Silverlight ID="mySLApp" runat="server" Source="~/ClientBin/JStoSLBridge.xap" MinimumVersion="2.0.31005.0" Width="100%" Height="100%" />
   7:     </div>
   8: </form>
View Plain

Not much to say about the addition of the TextBox other than to note the “onkeyup” JavaScript event handler which we’ll define next.

2.) Define the JavaScript event handler “UpdateLivePreview” as:

   1: <script type="text/javascript">
   2:  
   3: function UpdateLivePreview ()
   4: {
   5:     var sl = document.getElementById ('mySLApp');            // get silverlight app
   6:     var str = document.getElementById ('TextBox1').value;    // get current text from textbox
   7:  
   8:     sl.content.JStoSLBridge.UpdateDisplay(str);
   9: }
  10:  
  11: </script>
View Plain

In this function, we get a reference to the Silverlight control and also the text value of the TextBox control. We then pass in that TextBox value into the Silverlight control via the “JStoSLBridge” object which we’ll define inside the Silverlight control itself (see below).

That’s all we need to do on the ASP.NET/JavaScript side. Now we need to define and expose the UpdateDisplay method in our Silverlight app.

Update: Just a note to help you avoid spending half a day wondering why your getElementById function call is not working when you know it should… Because of ASP.NET master page control id mangling, you might want to use:

   1: function UpdateLivePreview()
   2: {
   3:     var sl = document.getElementById('<%=mySLApp.ClientID%>')      // get silverlight app
   4:     var str = document.getElementById('<%=TextBox1.ClientID%>').value;    // get current text from textbox
   5:  
   6:     sl.content.JStoSLBridge.UpdateDisplay(str);
   7: }
View Plain

The Silverlight Section of the Bridge

1.) In Page.xaml, add a row definition for the Grid and a TextBox (I also modified the sizing of the control, so here’s the entire page’s XAML for reference):

   1: <UserControl x:Class="JStoSLBridge.Page"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   4:     Width="680" Height="85">
   5:     <Grid x:Name="LayoutRoot" Background="White">
   6:         <Grid.RowDefinitions>
   7:             <RowDefinition></RowDefinition>
   8:         </Grid.RowDefinitions>
   9:  
  10:         <TextBox x:Name="txtPreview" TextWrapping="Wrap" Grid.Row="0" Height="85" Width="680" IsReadOnly="True"></TextBox>
  11:  
  12:     </Grid>
  13: </UserControl>
View Plain

I made the TextBox read-only by specifying IsReadOnly=”True”. I did this simply because I want the text to flow from the ASP.NET TextBox to the Silverlight TextBox, and don’t really want the user modifying the Silverlight TextBox directly.

2.) Now comes the really easy part: exposing a method in our Silverlight application so it can be called from JavaScript. It’s a two step process.

First, define the UpdateDisplay method with the special attribute, “ScriptableMember”:

   1: [ScriptableMember]
   2: public void UpdateDisplay (string strCommentText)
   3: {
   4:     txtPreview.Text = strCommentText;
   5: }
View Plain

ScriptableMember is part of the System.Windows.Browser namespace, so you’ll need to add a using statement at the top of the code file.

Second, register your scriptable object with the browser’s scripting engine by adding the following line of code to the Page class constructor:

   1: public Page ()
   2: {
   3:     InitializeComponent ();
   4:     HtmlPage.RegisterScriptableObject ("JStoSLBridge", this);
   5: }
View Plain

That’s the “JStoSLBridge” object we referenced above in the JavaScript function.

You can also expose the entire class to JavaScript by using the [ScriptableType] attribute:

   1: [ScriptableType]
   2: public partial class Page : UserControl
   3: {
View Plain

Since I just want the single method exposed, though, I’ll stick with the former approach.

Compile and Run

That’s it. Run and test.

Of course, make sure you’re running the correct page and that the ASP.NET web project is set as the startup project.

image

You should see text displayed in the Silverlight TextBox as you type in the ASP.NET TextBox:

image

Setting up a Continuous Integration System, Part 9: Conclusion

Setting up and maintaining—not to mention blogging about—a CI environment is a lot of work. It takes time, persistence, and a bit of dedication. Hopefully this series will give you a head start or at least a place to get more information on setting up a new CI environment or maintaining or even improving an existing one.

So, what’s next?

Let’s face it: no development project is ever truly done. The same goes for your CI environment. There’s always a new process, tool, or procedure to add to make it just a little (or maybe a whole lot) better. I hear a lot of good things about NAnt. It’s something I’d like to look into. Ditto for MSBuild scripting, which some people use to perform all of their CI tasks.

On the plug-in front, an immediate improvement to my updver (update version) plug-in would be if the plug-in accepted an array of assembly files to update rather than just the one per task declaration. The end result would be a cleaner and more readable ccnet.config file.

I’m sure there are other things. Perhaps some of them are future blog posts waiting to happen.

That’s it… for now

This concludes this series of posts. I’ll no doubt continue to edit the series as needed, keeping it updated with new ideas and that sort of thing. One of the charters of this blog is to serve as a documentation base for yours truly, so I know I’ll be visiting the posts occasionally for refreshers and just to figure out how or why I did something the way I did it.

Setting up a Continuous Integration System, Part 8: Configuring CruiseControl.NET

September 21, 2009 08:18

This is the next part in an ongoing series about setting up a continuous integration system. The series includes:

  1. Part 1: Introduction
  2. Part 2: Project Folders
  3. Part 3: CI Workflow
  4. Part 4: CI Server Baseline Software
  5. Part 5: CruiseControl.NET Custom Plug-in: Update Version
  6. Part 6: CruiseControl.NET Custom Plug-in: Source Retrieval
  7. Part 7: Installing CruiseControl.NET and Custom Plug-ins
  8. Part 8: Configuring CruiseControl.NET (this post)
  9. Part 9: Conclusion

We’re almost done. By this time you’ve got everything in place to embrace continuous integration. Everything but perhaps the most important part: the configuration of the CI server. Since I use CruiseControl.NET, I’ll next focus on the heart of that configuration, the ccnet.config file.

Resources for Configuring Your CruiseControl.NET Server

Before I jump into the configuration of the config file, let me point out some resources for getting more information. First, there’s ThoughtWorks’ Configuring the Server online help. More specifically, there’s the section on configuring the config file itself, which starts with the CruiseControl Configuration Block. From there, you can drill down into the information for each of the sub-blocks that live beneath <cruisecontrol>.

The Tree View perspective is another nice way of seeing the hierarchy of the ccnet.config file:

image

Editing the ccnet.config file

A quick word on editing the ccnet.config file: It’s just a text file, of course, but it helps to use something other than a plain text editor (like notepad) to edit the file. Any editor with syntax highlighting is going to make the process a lot easier. I use Visual Studio or, more often, Notepad2.

The barebones ccnet.config file

The ccnet.config file lives in C:Program FilesCruiseControl.NETserver. As installed, it’s pretty barren:

   1: <cruisecontrol xmlns:cb="urn:ccnet.config.builder">
   2:     <!-- This is your CruiseControl.NET Server Configuration file. Add your projects below! -->
   3:     <!--
   4:         <project name="MyFirstProject" />
   5:     -->
   6: </cruisecontrol>
View Plain

Not much happening there… yet. Let’s add to the file step-by-step until we have a project that CruiseControl.NET can run through the entire CI process.

I’ll show the full config file as I add each section to keep things in context.

1. Add a Project Configuration Block

The root tag pair is always <cruisecontrol>…</cruisecontrol>. From there, you add one or more <project>’s. I’ll use my Silverlight TagCloud project as an example. Adding the <project> tag with accompanying attributes yields:

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:     </project>
  11:     <!-- end SilverlightTagCloud -->
  12:  
  13: </cruisecontrol>
View Plain

I don’t use every attribute possible (see the online documentation for a complete list), but I’ll go over the ones I do use.

The <project> attributes I use are:

  • name: The name of the project. This is how it will appear through the CC.NET web interface as well as the cctray client app.
  • queue: CC.NET has this concept of integration queues, where by default each project will run in it’s own queue (or thread). Alternatively, you could run, say, debug and release builds one after the other by labeling them with the same queue name. Now, technically I don’t need this attribute defined, but I did when I used to have separate projects defined for each of debug and release builds. I leave it here now just in case I ever go back to that model.
  • queuePriority: This tells CC.NET the order in which to work on the projects in a queue. ‘1’ indicates highest priority, with ‘0’ denoting the project that will be worked on last after all others have been processed for that queue.
  • workingDirectory: The project’s working folder. I always use absolute paths to avoid confusion.
  • artifactDirectory: The project’s artifact folder where CC.NET task output is stored. Make sure this location is unique per project, otherwise things like NUnit, NCover, etc. sort of output will become mixed up. Again, I use absolute paths to keep things clear.
  • webUrl: The server where CC.NET runs. This is used by the cctray app so that when you double-click a project the web interface is brought up. If you’re hosting on a web server accessible from the Internet you can check the status of your builds from anywhere.
  • modificationDelaySeconds: The minimum amount of seconds to wait after the last check-in is performed. For example, if the modification delay is set to 10 seconds and the last check-in was 7 seconds ago, the system will sleep for 3 seconds before doing anything.

2. Add a Trigger Block

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:         
  11:         <triggers>
  12:             <!-- triggers help: http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger -->
  13:             <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists" />
  14:         </triggers>
  15:  
  16:     </project>
  17:     <!-- end SilverlightTagCloud -->
  18:  
  19: </cruisecontrol>
View Plain

Triggers tell CC.NET when to fire up a new CI cycle. While there are numerous types, I tend to go with the Interval Trigger, which sets a time interval for CC.NET to wake up and do it’s thing. It’s a one line declaration, stuck between the <triggers>…</triggers> tags and, in the above example, uses the following attributes:

  • name: The name of the trigger.
  • seconds: The number of seconds to sleep between integration cycles. I tend to keep this high for my home CC.NET server because I’m not that concerned with having to wait a little while for a build to kick off. At work, I tend to keep this number at 60 seconds.
  • buildCondition: The condition used to determine when to kick off a CI cycle. The value ‘IfModificationExists’ indicates that a build will occur only when source modifications are detected. Our <sourcecontrol> block takes care of the actual determination.

3. Add a State Manager Block

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:         
  11:         <triggers>
  12:             <!-- triggers help: http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger -->
  13:             <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists" />
  14:         </triggers>
  15:  
  16:         <state type="state" directory="C:ccnetstate" />
  17:         
  18:     </project>
  19:     <!-- end SilverlightTagCloud -->
  20:  
  21: </cruisecontrol>
View Plain

The State Manager Block tells CC.NET where to store such information as the build label, the time of the build, the outcome of the build, etc. It’s a pretty simple block. I use the same location for all projects. It doesn’t seem to cause any issues.

4. Add a Source Control Block

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:         
  11:         <triggers>
  12:             <!-- triggers help: http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger -->
  13:             <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists" />
  14:         </triggers>
  15:  
  16:         <state type="state" directory="C:ccnetstate" />
  17:         
  18:         <sourcecontrol type="svnget">
  19:             <executable>c:program filesVisualSVNbinsvn.exe</executable>
  20:             <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  21:         </sourcecontrol>
  22:         
  23:     </project>
  24:     <!-- end SilverlightTagCloud -->
  25:  
  26: </cruisecontrol>
View Plain

The Source Control Block defines from where CC.NET should pull source. My experience up to this point involves use of the blocks for Visual SourceSafe, Subversion, and Team Foundation Server. They all function more or less the same; source control is source control, from the perspective of CC.NET, anyway. In a previous post in this series I discussed my own custom source control plug-in for Subversion. I also explained why I use my own as opposed to using the stock one, so I won’t go into that now. You should note that using one or the other is really not that dissimilar.

Briefly, the source control block’s attributes:

  • type: The source control product to use.
  • executable: The location of the svn executable.
  • workingDirectory: The local project folder.

5. Add a Labeller Block

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:         
  11:         <triggers>
  12:             <!-- triggers help: http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger -->
  13:             <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists" />
  14:         </triggers>
  15:  
  16:         <state type="state" directory="C:ccnetstate" />
  17:         
  18:         <sourcecontrol type="svnget">
  19:             <executable>c:program filesVisualSVNbinsvn.exe</executable>
  20:             <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  21:         </sourcecontrol>
  22:         
  23:         <labeller type="iterationlabeller">
  24:             <prefix>1.0</prefix>
  25:             <duration>1</duration>
  26:             <releaseStartDate>2009/8/15</releaseStartDate>
  27:             <separator>.</separator>
  28:         </labeller>
  29:         
  30:     </project>
  31:     <!-- end SilverlightTagCloud -->
  32:  
  33: </cruisecontrol>
View Plain

The Labeller Block is used by CC.NET to generate build version labels. This label can then be used to version your assemblies. I like the Iteration Labeller, which takes the following attributes:

  • prefix: Any string to prepend to a label.
  • duration: The duration of the iteration in weeks; helps define how the label will be incremented.
  • releaseStartDate: The start date for iteration one. I typically use the date in which I added the project to CC.NET, just to start things off somewhere.
  • separator: The separator between iteration number and build number.

6. Add a Prebuild Block

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:         
  11:         <triggers>
  12:             <!-- triggers help: http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger -->
  13:             <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists" />
  14:         </triggers>
  15:  
  16:         <state type="state" directory="C:ccnetstate" />
  17:         
  18:         <sourcecontrol type="svnget">
  19:             <executable>c:program filesVisualSVNbinsvn.exe</executable>
  20:             <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  21:         </sourcecontrol>
  22:         
  23:         <labeller type="iterationlabeller">
  24:             <prefix>1.0</prefix>
  25:             <duration>1</duration>
  26:             <releaseStartDate>2009/8/15</releaseStartDate>
  27:             <separator>.</separator>
  28:         </labeller>
  29:         
  30:         <prebuild>
  31:             <!-- assembly version update prior to build -->
  32:             <svnupdver>
  33:                <executable>c:program filesVisualSVNbinsvn.exe</executable>
  34:                <assemblyInfoFolder>C:devsolutionsSilverlightTagCloudprojectsTagCloudControlProperties</assemblyInfoFolder>
  35:             </svnupdver>
  36:            <svnupdver>
  37:               <executable>c:program filesVisualSVNbinsvn.exe</executable>
  38:               <assemblyInfoFolder>C:devsolutionsSilverlightTagCloudprojectsTagCloudServiceProperties</assemblyInfoFolder>
  39:            </svnupdver>
  40:         </prebuild>
  41:         
  42:     </project>
  43:     <!-- end SilverlightTagCloud -->
  44:  
  45: </cruisecontrol>
View Plain

The Prebuild Block is a container for Task Blocks. I typically perform my assembly version update here via my own custom plug-in, though you can just as easily do it in the main Tasks Block.

7. Add a Task Block

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:         
  11:         <triggers>
  12:             <!-- triggers help: http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger -->
  13:             <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists" />
  14:         </triggers>
  15:  
  16:         <state type="state" directory="C:ccnetstate" />
  17:         
  18:         <sourcecontrol type="svnget">
  19:             <executable>c:program filesVisualSVNbinsvn.exe</executable>
  20:             <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  21:         </sourcecontrol>
  22:         
  23:         <labeller type="iterationlabeller">
  24:             <prefix>1.0</prefix>
  25:             <duration>1</duration>
  26:             <releaseStartDate>2009/8/15</releaseStartDate>
  27:             <separator>.</separator>
  28:         </labeller>
  29:         
  30:         <prebuild>
  31:            <!-- assembly version update prior to build -->
  32:            <svnupdver>
  33:               <executable>c:program filesVisualSVNbinsvn.exe</executable>
  34:               <assemblyInfoFolder>C:devsolutionsSilverlightTagCloudprojectsTagCloudControlProperties</assemblyInfoFolder>
  35:           </svnupdver>
  36:           <svnupdver>
  37:              <executable>c:program filesVisualSVNbinsvn.exe</executable>
  38:              <assemblyInfoFolder>C:devsolutionsSilverlightTagCloudprojectsTagCloudServiceProperties</assemblyInfoFolder>
  39:           </svnupdver>
  40:         </prebuild>
  41:         
  42:         <tasks>
  43:             <!-- msbuild task help: http://confluence.public.thoughtworks.org/display/CCNET/MsBuild+Task -->
  44:             <msbuild>
  45:                 <executable>C:WIN2003Microsoft.NETFrameworkv3.5MSBuild.exe</executable>
  46:                 <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  47:                 <projectFile>SilverlightTagCloud.sln</projectFile>
  48:                 <buildArgs>/noconsolelogger /p:Configuration=Debug /v:Minimal</buildArgs>
  49:                 <targets>Build</targets>
  50:                 <timeout>900</timeout>
  51:                 <logger>c:Program FilesCruiseControl.NETserverRodemeyer.MsBuildToCCNet.dll</logger>
  52:             </msbuild>
  53:             <msbuild>
  54:                 <executable>C:WIN2003Microsoft.NETFrameworkv3.5MSBuild.exe</executable>
  55:                 <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  56:                 <projectFile>SilverlightTagCloud.sln</projectFile>
  57:                 <buildArgs>/noconsolelogger /p:Configuration=Release /v:Minimal</buildArgs>
  58:                 <targets>Build</targets>
  59:                 <timeout>900</timeout>
  60:                 <logger>c:Program FilesCruiseControl.NETserverRodemeyer.MsBuildToCCNet.dll</logger>
  61:             </msbuild>
  62:             <exec>
  63:                 <executable>C:devdeployziprelease.bat</executable>
  64:                 <environment>
  65:                     <variable>
  66:                         <name>BUILDNAME</name>
  67:                         <value>SilverlightTagCloud</value>
  68:                     </variable>
  69:                 </environment>
  70:             </exec>
  71:         </tasks>
  72:         
  73:     </project>
  74:     <!-- end SilverlightTagCloud -->
  75:  
  76: </cruisecontrol>
View Plain

The Task Block is the main workhorse of the CC.NET CI system. It is where you would typically put your build tasks, run unit tests, etc. For the full list of tasks, see the Task Blockdocumentation.

As you can see above, the SilverlightTagCloud project doesn’t have a lot of tasks. It builds Debug, it builds Release, and it zips the release via a batch file within an Exec Task. Other steps I like to add to the tasks section are NCover via an Exec Task (you can alternatively use the NCover Reporting Task) and NUnit Testing via the NUnit Task, but because Silverlight still has limited unit testing tools and, well, I don’t see the point of adding code coverage to something that has limited unit testing to begin with, those sections are not here. In the interest of being thorough, however, I’ll include examples from another project so you can at least see them defined.

First, the MSBuild Tasks’ attributes:

  • executable: The location of the msbuild.exe executable.
  • workingDirectory: The project’s root folder.
  • projectFile: The solution or project file to build.
  • buildArgs: Any arguments to pass to msbuild.
  • targets: A list of targets to run (i.e., Build; Test).
  • timeout: Number of seconds to wait before assuming we’re hung and to time the build out.
  • logger: If you’re using a custom logger, specify that here.

Now, the Exec Task. An Exec Task is a general purpose task wherein you can run just about anything you want. Here, I use it to run a batch file that zips up the project’s source, build output, etc. (using the 7zip command line application). One of the nice features of CC.NET is that it will store things like the build label as an environment variable that you can then use in a batch file. I use the label as part of the zip file’s name, so something like “SilverlightTagCloud v1.0.7.8.zip”. This file, which constitutes a release candidate, is then stored away in a shared build archive folder.

The attributes of the Exec Task that I use are:

  • executable: The program to run.
  • environment: Used to specify environment variables to set for the running executable. Here I set BUILDNAME to “SilverlightTagCloud”, which is used to construct the name of the .zip file.

Now, to throw in how I use NCover and NUnit.

For NCover, you can use the NCover Reporting Task or, as I do, an exec task (see the Using CruiseControl.NET with NCover tutorial for more info; remember to add a merge task):

   1: <exec>
   2:     <executable>"C:Program FilesNCoverNCover.Console.exe"</executable>
   3:     <buildArgs>"C:Program FilesNUnit 2.4.7binnunit-console.exe"
   4:         //w "C:projectsolutionsprojectnamebindebug"
   5:         //x "C:projectsolutionsprojectnamebinccnetartifactsncoverproject-coverage.xml"
   6:         //l "C:projectsolutionsprojectnamebinccnetartifactsncoverproject-coverage.log"
   7:         "C:projectsolutionsprojectnamebinclientdebugprojectname.dll"</buildArgs>
   8: </exec>
View Plain

For NUnit, I use the NUnit Task:

   1: <nunit path="C:Program FilesNUnit 2.4.7binnunit-console.exe">
   2:     <assemblies>
   3:         <assembly>C:projectsolutionsprojectnamebinclientdebugprojectname.dll</assembly>
   4:     </assemblies>
   5: </nunit>
View Plain

8. Add Publishers

I use two publishers: one to log modification information (as in when a code file has changed) and another, the XML Log Publisher, which the CC.NET documentation states, “The Xml Log Publisher is used to create the log files used by the CruiseControl.NET Web Dashboard, so if you don’t define an <xmllogger /> section the Dashboard will not function correctly”. Not much more to say about it beyond that. See the conclusion for usage of the <publishers> task.

Conclusion

Here, then, is the complete ccnet.config file for the SilverlightTagCloud project:

   1: <cruisecontrol>
   2:  
   3:     <!-- begin SilverlightTagCloud -->
   4:     <!-- online help: http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block -->
   5:     <project name="SilverlightTagCloud" queue="SilverlightTagCloud" queuePriority="1">
   6:         <workingDirectory>C:devsolutionsSilverlightTagCloud</workingDirectory>
   7:         <artifactDirectory>C:devsolutionsSilverlightTagCloudbinccnetartifacts</artifactDirectory>
   8:         <webURL>http://server_name_or_ip/ccnet/server/local/project/SilverlightTagCloud/ViewLatestBuildReport.aspx</webURL>
   9:         <modificationDelaySeconds>20</modificationDelaySeconds>
  10:         
  11:         <triggers>
  12:             <!-- triggers help: http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger -->
  13:             <intervalTrigger name="continuous" seconds="300" buildCondition="IfModificationExists" />
  14:         </triggers>
  15:  
  16:         <state type="state" directory="C:ccnetstate" />
  17:         
  18:         <sourcecontrol type="svnget">
  19:             <executable>c:program filesVisualSVNbinsvn.exe</executable>
  20:             <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  21:         </sourcecontrol>
  22:         
  23:         <labeller type="iterationlabeller">
  24:             <prefix>1.0</prefix>
  25:             <duration>1</duration>
  26:             <releaseStartDate>2009/8/15</releaseStartDate>
  27:             <separator>.</separator>
  28:         </labeller>
  29:         
  30:         <prebuild>
  31:            <!-- assembly version update prior to build -->
  32:            <svnupdver>
  33:               <executable>c:program filesVisualSVNbinsvn.exe</executable>
  34:               <assemblyInfoFolder>C:devsolutionsSilverlightTagCloudprojectsTagCloudControlProperties</assemblyInfoFolder>
  35:           </svnupdver>
  36:           <svnupdver>
  37:              <executable>c:program filesVisualSVNbinsvn.exe</executable>
  38:              <assemblyInfoFolder>C:devsolutionsSilverlightTagCloudprojectsTagCloudServiceProperties</assemblyInfoFolder>
  39:           </svnupdver>
  40:         </prebuild>
  41:         
  42:         <tasks>
  43:             <!-- msbuild task help: http://confluence.public.thoughtworks.org/display/CCNET/MsBuild+Task -->
  44:             <msbuild>
  45:                 <executable>C:WIN2003Microsoft.NETFrameworkv3.5MSBuild.exe</executable>
  46:                 <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  47:                 <projectFile>SilverlightTagCloud.sln</projectFile>
  48:                 <buildArgs>/noconsolelogger /p:Configuration=Debug /v:Minimal</buildArgs>
  49:                 <targets>Build</targets>
  50:                 <timeout>900</timeout>
  51:                 <logger>c:Program FilesCruiseControl.NETserverRodemeyer.MsBuildToCCNet.dll</logger>
  52:             </msbuild>
  53:             <msbuild>
  54:                 <executable>C:WIN2003Microsoft.NETFrameworkv3.5MSBuild.exe</executable>
  55:                 <workingDirectory>c:devsolutionsSilverlightTagCloud</workingDirectory>
  56:                 <projectFile>SilverlightTagCloud.sln</projectFile>
  57:                 <buildArgs>/noconsolelogger /p:Configuration=Release /v:Minimal</buildArgs>
  58:                 <targets>Build</targets>
  59:                 <timeout>900</timeout>
  60:                 <logger>c:Program FilesCruiseControl.NETserverRodemeyer.MsBuildToCCNet.dll</logger>
  61:             </msbuild>
  62:             <exec>
  63:                 <executable>C:devdeployziprelease.bat</executable>
  64:                 <environment>
  65:                     <variable>
  66:                         <name>BUILDNAME</name>
  67:                         <value>SilverlightTagCloud</value>
  68:                     </variable>
  69:                 </environment>
  70:             </exec>
  71:         </tasks>
  72:         
  73:         <publishers>
  74:             <!-- modificationHistory help: http://confluence.public.thoughtworks.org/display/CCNET/ModificationHistory+Publisher -->
  75:             <modificationHistory onlyLogWhenChangesFound="true" />
  76:             <xmllogger logDir="buildlogs" />
  77:             <statistics />
  78:         </publishers>
  79:         
  80:     </project>
  81:     <!-- end SilverlightTagCloud -->
  82:  
  83: </cruisecontrol>

Interface vs. Virtual vs. Abstract

Sometimes I like to take a step back and look a little harder at something I use almost every day without ever really thinking about it. Take virtual functions, for example, which I was using in C++ long before C# came into existence. C# (and Java) introduced some new keywords to the (somewhat confusing) mix, namely interface and abstract. Think of this post as a refresher on these concepts and their differences.

Interface

MSDN defines an interface as consisting of:

[…] methods, properties, events, indexers, or any combination of those four member types. An interface cannot contain constants, fields, operators, instance constructors, destructors, or types. It cannot contain static members. Interfaces members are automatically public, and they cannot include any access modifiers.

In addition, an interface type does not define any implementation whatsoever. It is a contract only, requiring derived objects to provide the implementation details. It is not possible therefore to instantiate an interface directly. An interface is very much like a class containing pure virtual functions.

As a contract, once an interface is locked down you’ll want to minimize changes to it as any change breaks any classes deriving from it.

As an example, let’s say you want to apply an object model to your pets (if you don’t have pets, why not?). You might start with an interface “IPet”:

   1: interface IPet
   2: {
   3:     void Eat();
   4:
   5:     void Run();
   6:
   7:     void Play();
   8: }
View Plain

As you can see the IPet interface only defines methods; it doesn’t provide implementation details.

Now let’s provide an implementation class for this interface by deriving an object from it:

   1: public class Dog : IPet
   2: {
   3:     public void Eat()
   4:     {
   5:         // eat
   6:     }
   7:
   8:     public void Run()
   9:     {
  10:         // run
  11:     }
  12:
  13:     public void Play()
  14:     {
  15:         // play
  16:     }
  17: }
View Plain

As you can see, I’ve derived from IPet and implemented the three interface methods, Eat(), Run(), and Play(). I’m required to implement all three of them because of the contract established by the IPet interface. Not doing so will result in a compiler error.

Virtual

The whole point of the virtual keyword is to allow derived classes to override parent functionality and, as objects related by inheritance, for each of those objects to respond differently to the same message (i.e., polymorphism). Virtual class methods can define implementations, but those implementations can be overridden with the override keyword.

I’ll modify my Dog class so that Play() is virtual:

   1: public class Dog : IPet
   2: {
   3:     public void Eat()
   4:     {
   5:         // eat
   6:     }
   7:
   8:     public void Run()
   9:     {
  10:         // run
  11:     }
  12:
  13:     public virtual void Play()
  14:     {
  15:         // play
  16:     }
  17: }
View Plain

Then I’ll create a new class called “Labrador” that derives from Dog and uses the override keyword to provide a new implementation of Play() (let’s assume there’s something specific about how labs play that’s different from other dogs):

   1: class Labrador : Dog
   2: {
   3:     public override void Play()
   4:     {
   5:         // labrador playing
   6:     }
   7: }
View Plain

I might have another Dog-derived class called Dalmatian and also override the Play() method there. Given a collection, IList<Dog>, made up of both Labrador and Dalmatian objects, I could iterate through this list, call Play() on each of the Dog objects, and have the overridden implementations called rather than the parent class’s implementation.

Here’s the code to do that:

   1: IList<Dog> dogCollection = new List<Dog> (4);
   2: dogCollection.Add (new Labrador ());
   3: dogCollection.Add (new Dalmatian ());
   4: dogCollection.Add (new Labrador ());
   5: dogCollection.Add (new Dalmatian ());
   6:
   7: foreach (var dog in dogCollection)
   8: {
   9:     dog.Play();
  10: }
View Plain

Putting that code into a console app and adding some Trace WriteLine’s to each of the overridden Play() methods yields:

image

This is polymorphism at its best.

Abstract

An abstract class can contain both implemented members and non-implemented members. Like an interface, an abstract class cannot be instantiated directly. This means that any derived classes must implement any methods defined as abstract in the parent class. Methods that do have implementations in the abstract class can be used as-is by child classes or can be overridden if those methods are defined as virtual.

I modified my Dog class to contain a new abstract method, Chase(), and also made the class abstract since any class that has abstract methods must also be declared abstract.

   1: public abstract class Dog : IPet
   2: {
   3:     public void Eat()
   4:     {
   5:         // eat
   6:     }
   7:
   8:     public void Run()
   9:     {
  10:         // run
  11:     }
  12:
  13:     public virtual void Play()
  14:     {
  15:         // play
  16:     }
  17:
  18:     public abstract void Chase();
  19: }
View Plain

If we take a look at the Labrador class, we can see that we now have to provide an implementation for the Chase() method:

image

Here’s the Labrador class with the new method:

   1: public class Labrador : Dog
   2: {
   3:     public override void Play()
   4:     {
   5:         Trace.WriteLine ("labrador playing");
   6:     }
   7:
   8:     public override void Chase()
   9:     {
  10:         Trace.WriteLine ("labrador chasing");
  11:     }
  12: }
View Plain

Abstract differs from virtual in that a virtual method can have an overridable implementation whereas an abstract method has no implementation at all and, as such, abstract methods must be implemented in any derived classes. In effect, an abstract class can have default (non-abstract) implementations that child classes can immediately take advantage of, but also possess abstract methods that a designer wants child classes to have to implement themselves.

Conclusion

Given the above, I wish I could say there is a hard and fast rule for using each of the keywords. The truth is that their use is very design dependent. In a general sense, though, one would use an interface when one wants to enforce a contract, the methods of which must be implemented by all derived classes. One would use virtual when one wants to define default implementation details while allowing for the option of derived classes overriding and defining their own implementations. Last, one would use abstract when one wants to provide default implementation details while also forcing derived classes to provide their own implementation of those methods defined as abstract.

References

  • Stackoverflow: What is the difference between abstract function and virtual function?
  • Stackoverflow: Why do both the abstract class and interface exist in .Net?
  • MSDN: Abstract
  • MSDN: Virtual
  • MSDN: Interface

 

Coding Standards & Conventions

March 20, 2009 09:28

This is my attempt to list those resources having to do with coding standards that I’ve found useful.

If you have your own favorites, let me know and I’ll add them or just link them in the comments.

Here’s the list.

.NET/C#

  • MSDN: Design Guidelines for Class Library Developers
  • Brad Abrams’ Framework Design Guidelines
  • .NET Framework Developer’s Guide: Guidelines for Names
  • irritatedVowel.com: .NET Programming Standards and Naming Conventions
  • Phillips Electronics C# Coding Standard (.pdf)
  • IDesign C# Coding Standard (.zip)
  • Lance’s Whiteboard: C# Coding Standards
  • Custom Control Development: Simple code guidelines

Java

  • Coding Conventions for the Java Programming Language

C Sharp Coding Shortcuts

January 29, 2009 09:01

The C# language as well as the Visual Studio environment contain a number of shortcuts meant to ease our development lives. Here’s a few of them.

1. Initializing Variables

Sometimes, it is not necessary to initialize variables because they are already initialized implicitly. For example, writing this is redundant:

   1: bool bFlag = false;
View Plain

A type of ‘bool’ is implicitly initialized to ‘false’, so while adding the “= false” is nice from an explicit standpoint, it is unnecessary.

Further, this is also redundant:

   1: string strTestString = string.Empty;
View Plain

A ‘string’ type is implicitly set to empty (though if class scope it will be null, so be careful).

Also,

   1: int i = 0;
View Plain

Types of ‘int’ are implicitly set to ‘0’;

2. If…Then

Sure, you can code this:

   1: if (strYear == null)
   2: {
   3:     return ("null");
   4: }
   5: else
   6: {
   7:     return (strYear);
   8: }
View Plain

But there’s a more concise shorthand available from the C++ world that was carried into C#:

   1: (strYear != null)?strYear:"null"
View Plain

Even more concise is this C# construct:

   1: strYear ?? "null"
View Plain

A full example might be:

   1: StringBuilder sb = new StringBuilder();
   2: sb.AppendFormat ("Year [{0}]", strYear ?? "null");
View Plain

3. Use TAB to Add Event Handlers

You can add event handlers by hand, or you can take advantage of the “Press TAB to insert” functionality.

Say you’ve got a button to which you want to add the “Click” event. Once you’ve typed the below “b.Click +=” you’ll see a little pop-up prompting you to hit TAB to insert.

image

Hitting TAB will get you to here:

image

Hitting TAB again will give you the final code, which includes the event handler itself:

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:     Button b = new Button();
   4:  
   5:     b.Click += new EventHandler (b_Click);
   6: }
   7:  
   8: void b_Click (object sender, EventArgs e)
   9: {
  10:     throw new NotImplementedException ();
  11: }
View Plain

4. Use var to declare variables (C# 3.0 specific)

From the MSDN documentation:

Beginning in Visual C# 3.0, variables that are declared at method scope can have an implicit type var. An implicitly typed local variable is strongly typed just as if you had declared the type yourself, but the compiler determines the type.

What that means is if you have this:

   1: string strSampleString = "This is a test";
View Plain

You could alternatively declare it as:

   1: var strSampleString = "This is a test";
View Plain

This declaration allows the compiler to determine the type behind the scenes, but still at compile time. Also, “var” does not mean “variant”, nor is it late-bound.

More information about implicitly typed variables can be found here.

5.) Use string.IsNullOrEmpty

Instead of writing this:

   1: if ( (strId == null) || (strId.Length == 0))
   2: {
   3:     return;
   4: }

Use this:

   1: if (string.IsNullOrEmpty (strLocatorId))
   2: {
   3:     return;
   4: }