FTP Task in WF and TFS

Now that I have custom MSBuild tasks working using WF (and kicking them off using TFS), I realized that I DON”T want to use custom MSBuild tasks.  It seems easier just to put in a custom WF task as part of the bdefult build template that comes out of the box in TFS.  To that end, I created a new template and identified the step where I would put in this new workflow task (it took 5 minutes to click through this TFS WF, it is a beast!):

image

 

I then created a new project to get the FTP tasks working.  Before using WF, I thought of just creating a Console app that does what I want. I monkeyed around with the the native .NET FTP library but is stinks and then I tried FTPLibrary – which didn’t work out of the box. I then tried EditFTP and it worked great so I used that.  I created a structured program that uses the Edit FTP API to copy the contents from the build directory to the FTP site (oooh, recursion):

static FTPConnection ftpConnection = null; static string hostName = @"ftp.xxx.com"; static string userName = "xxx"; static string password = "xxx"; static string sourceLocation = @"xxx"; static string targetLocation = "xxx"; static void Main() { Console.WriteLine("Start"); ConfigureFTPConnnection(); CopyDirectoryContents(sourceLocation, targetLocation); Console.WriteLine("End"); Console.ReadKey(); } static void ConfigureFTPConnnection() { ftpConnection = new FTPConnection(); ftpConnection.UserName = userName; ftpConnection.Password = password; ftpConnection.ServerAddress = hostName; ftpConnection.Connect(); } static void CopyDirectoryContents(string sourceDirectoryName, string targetDirectoryName) { DirectoryInfo directoryInfo = new DirectoryInfo(sourceDirectoryName); ftpConnection.ChangeWorkingDirectory(targetDirectoryName); foreach (FileInfo fileInfo in directoryInfo.GetFiles()) { ftpConnection.UploadFile(fileInfo.FullName, fileInfo.Name); } foreach (DirectoryInfo subDirectoryInfo in directoryInfo.GetDirectories()) { ftpConnection.CreateDirectory(subDirectoryInfo.Name); CopyDirectoryContents(subDirectoryInfo.FullName, subDirectoryInfo.Name); } ftpConnection.ChangeWorkingDirectoryUp(); }

Pretty simple stuff – note the use of ChangingWorkingDirectoryUp to keep the current directory on the remote site synched.

I then thought about how to create a WF class that does the same thing.  To do that, I fired up a VS2010 Activity diagram.  My 1st cut was WAAY too complicated:

image

I refined it based on the fact that my FTP API automagically overwrites files:

image

Much easier.  I then created a workflow activity (with its .asmx extension).  I realized that I would simply throw a code activity on to the designer, move the procedureal code I already wrote into that activity, and call it a a day.  However, I wanted to see if I can exploit the power of WF and replicate the activity diagram using workflow tasks.  I’ll document my experiences with that attempt next week.

MSBuild Custom Task and TFS

I tried to kick off the a custom build task using TFS like so:

image

The .csproj has 1 file:

image

And the 1 class has the following code:

using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Com.Tff.Carpool.Build { public class PublishWebSite : Task { public override bool Execute() { return true; } } }

However, the build just hangs:

image

I then realized I never included the Build in the .csproj definition. I unloaded the project and added the following line of code:

image

I then reloaded the project and it still just hanged. I then realized that my Build Controller was not running <sound of me slapping my head />

image

Once I started the build controller, the job kicked off and ran:

image

I then added the workflow from this previous post to the Build and it ran.   I then went to the build definition’s Process tab and realized I was using the Default template:

image 

It seems to me that the path of least resistance would be to have a couple of WF tasks in the Template that takes the website and FTP it out. I would do this versus creating a new project that has the workflow and then has to find the files in the drop folder.  Therefore, no MSBUild tasks.

I first created a new template based on the default:

image

Note that I have to manually add the template to source control:

image

I then double clicked on it to alter the definition- and waited 15 seconds for the screen to refresh – holy cow this workflow is a beast!

image

I want to add 1 task at the end – ftp contents to a remote directory. I wonder how I am going to unit test this. I realized that I need to create the FTP Workflow in a different project, test it, and then move it over into this template.  That will be the subject of my next post.

MSBuild and Workflow Foundation

I am in the process of investigating how to create a FTP Task to be used in the TFS Build Engine.  This post is about creating a MSBuild task, kicking it off, and then integrating WF into that task. 

Using this MSDN article, I crated a basic task like so:

using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Tff.FtpTask { public class PublishWebsite: Task { public override bool Execute() { return true; } } }

I then created a build file like so:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="PublishWebsite"> <PublishWebsite /> </Target> </Project>

When I ran it:

image

The build file is not finding the task I created. I altered the build file like this:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask TaskName="PublishWebsite" AssemblyFile="C:\Users\Jamie\Documents\Visual Studio 2010\Projects\Tff.FtpTask\Tff.FtpTask\bin\Debug\Tff.FtpTask.dll" /> <Target Name="PublishWebsite"> <PublishWebsite /> </Target> </Project>

And pop goes the fire cracker:

image

I then added in a workflow to the project:

image

Called the workflow:

public override bool Execute() { WorkflowInvoker.Invoke(new PublishWebsite()); return true; }

And Bang!  That was pretty straight forward.

image

MSBuild: Part 2

I am going through this tutorial about automating the build with MSBuild. The first task with the Build Script seems fairly basic – adding a command to delete the files in the /bin directory. The interesting thing I found was that the valid code does not have intellisense support:

clip_image002

But when I run it:

clip_image004

Sure enough, the files are gone.

clip_image006

I guess BinFiles was added after the created the code comments? Baffling.

MS Build: Part 1

I was hoping that TFS2010 has a “publish website” wizard that Visual Studio 2010 has using FTP. 

image

 

Alas, they do not.  There is some community tasks, but I thought I could use the exercise to dive into MSBuild and perhaps Workflow Foundation.

My first stop was this MSDN article. I altered the .csproj file with the HelloWorld Target and Message. I found a couple of things.

The article is wrong – it tells you to run msbuild helloworld.csproj /t:helloworld but the name of the project is BuildApp. The correct commandline syntax is msbuild BuildApp.csproj /t:helloworld

When I ran msbuild the 1st time, I got this error:

image

The reason was back in my csproj file:

<Target Name="Hello World">

Should by:

<Target Name="HelloWorld">

Once I did that:

image

Pop goes the firecracker….

On a related note, I entered in the error to MSDN – I wonder how fast they will take to fix it?

My First Build

I set up a new build

Hit the General Tab:

image

Updated the Trigger:

image

Change the workspace from the main branch to the release one:

image

Put in a build target on my local file system:

image

And then finally specified the items to build:

image

I then opened my Release project and changed the configuration to Release:

image

I clicked on Build Solution and nothing happened -> good.

I then checked in the Project and nothing happened -> bad.

I went to the Build Explorer and clicked on “Queue Build” and selected the build that I just created. Luckily, the build tried to run and it failed:

image

And the error was that the folder didn’t exist:

image

And this is the reason why:

image

Changing the permission to the folder for everyone to get full control -> and it is still not working.

I then Binged the error code: TFS Build Failed to create directory. Details: The network name cannot be found.

I gave up and stopped trying to write to that file and pointed to \\DIXON08\Users\Public\Builds and made some progress.

I ran across this error next: The build controller Default Controller – dixon08 does not contain an enabled build agent with name * and no tags.

I then went to TFS Admin Console – yup, no build agent (I must have deleted it when I was getting rid of the project groups). I added an agent:

image

And Boom goes the dynamite:

image

And:

image

I then added a project alert for the build (team-> project alerts):

image

Now, I need to move the build onto our production server, perhaps like this.

Now, I want to ftp my build out to winhost.

TFS Branching (Warning: Lots of Screen Shots)

For my carpool project, I attempted to create a branching strategy based on the ALM Ranger’s recommendations of a basic strategy (main, dev, release).  For my 1st task, I attempted to branch for the new feature set. However, after 1 hour, I realized that I set up my TFS projects incorrectly. I went ahead and deleted all of my project groups EXCEPT the default one. From there, I added a new Carpool Project.

 

image

image

image

Note that creating the project is a TFS-specific action, the actual file system has not been altered yet. I then needed to add this project to my file system:

image

image

Note that I added it to the Root folder for my VS2010 Projects. All of the child branches will be off of this.

image

Right now in the folder, there is nothing there (except for the build process templates).

The quick start guide for VSTS Ranger team recommends 3 branches – Main, Dev, and Release. Following that guide, I created the Main Branch off of the root and then the Development and Release branches off of Main.

image

An interesting TFS bug (I mean feature) is that if you make a new folder and then click out of VS2010, the folder is still created on the file system, but not in the Source Control Folder.

Under each branch, the guide recommends having a Bin, Docs, and SRC (Source) directory. Since I am not using a build agent (yet), this seems unnecessary. I put my source code directly in the Main branch on the file system:

image

I then added the files to the folder:

image

image

I now have my original source files in the Main Folder.

I then checked in the 118 files.

I then converted the Mian Folder to a Branch -> according to the documentation, it allows me to see it in the designer.

image

I then needed to create 2 branches off of the Main – Development and Release

image

Then I had to get latest version:

image

Automagically, 2 things happen.

My Development Folder was created into a Branch:

image

In addition, all of the main branches files were copied over to the development branch:

image

I did the same with the Release branch

Volia: 3 branches:

image

And 2 are subordinate to the Main as you can see by clicking on properties on Main and selecting Relationships:

image

 

image

And also, for the capstone:

 

image

 

image

I think I have things set up right – though I don’t think I need Main and Release files on my file system – just development. In any case, I will now try and branch to the globalization change set.

Before starting on Version 1.1 (Gloabization), I wanted to see if I could make changes correctly. I checked out the Production Version and made 1 change to the master page (added a trademark symbol).

I incremented the file and assembly version:

image

And I checked in the file

I then went to Version Control Explorer and Added a Label:

image

I then Merged the Release branch up to the Main branch:

image

I then went to Main branch and checked in the pending changes:

image

And the changes are now in Main and Release – Yippie!

I then merged the changes into Development. I went ahead and made the changes in development to see how the merge tool handles it:

image

And it is flat out awesome!

The only thing I need to keep in mind is that I might have the development branch solution in VS2010 and working with the version control explorer, forget that and think I am in a different environment.

Note that I will release from the Release Branch, not the Main branch…

I am now ready to start Globalization…

Code Coverage In Visual Studio 2010

To run code coverage, you first need test settings for your solution. Right Click on your solution in Explorer and select new -> Test Settings

image

Then, you can hit configure and add in code coverage.

Once you add it in, you need to use the Test Output window:

image

Also, once your assembly is strong named, you need to add that to your tests:

image

Note that you are not running code coverage on your test .dlls, but on your working code assemblies.

 

VS2010 does many things well, the code coverage Easter egg hunt is not one of them.

Refactoring The Carpool Solution Using Code Analysis

Carpool refactoring – running code analysis.

I wanted to spend some time with the refactoring tools in VS2010 Ultimate so I decided to analyze the carpool solution. My initial showed that the solution was written “pretty well”:

clip_image002

I then wanted to see how well I complied with FxCop. I started with the Domain project because it had the most lines of code and had the most custom-code by the nature of the business rules.

clip_image004

Of the 84 errors, there were some reoccurring problems.

I am using a strongly-typed List to expose collections from the API (List<T>). FxCop recommends using the Collection<T>. I clicked on “Show Error Help” in the context menu and got to MSDN’s Do Not Expose Generic Lists. It seems that using List<T> versus Collection<T> is one of trade-offs. You should use List<T> is you are worried about performance and you should use Collection<T> is you are worried about extendibility. Since I am not planning to use the Carpool Domain in other solutions (Famous Last Words), I decided to keep the List<T> in this solution. I will try and use Collections<T> in my next project.

There are three possibilities in suppressing messages:

The individual message in the source, which puts an attribute on the function:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")] public static List<Carpool> GetCarpools(DateTime startDate, DateTime endDate, string userName) {

The individual message in the project suppression file, which puts the message in a GlobalSupression.cs file which looks like this:

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Com.Tff.Carpool.Domain.CarpoolFactory.#GetAllCarpools()")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "Com.Tff.Carpool.Domain.CarpoolSummaryFactory.#GetCarpoolSummaries(System.DateTime,System.DateTime)")]

If I want to suppress the error for ALL methods, I thought that I can change the suppression message to this:

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Scope = "member", Target = "")]

 

However, that didn’t seem to work. In liu of a global suppression, I just held the shift key down on messages of the same type and suppressed all of them at once

The next message was

Warning 55 CA1012 : Microsoft.Design : Change the accessibility of all public constructors in ‘ValidationBase’ to protected.

Apparently, Abstract types should not have constructors – which makes sense. I changed it to protected, ran my unit tests (I used CTRL-R, Y to only run impacted tests), and moved on.

The next error showed me that having some for thought on your design up front will save some headaches. The error was:

Warning 4 CA1014 : Microsoft.Design : Mark ‘Com.Tff.Carpool.Domain.Tests.dll’ with CLSCompliant(true) because it exposes externally visible types.

The problem is that my solution is NOT CLSCompliant. All of my public parameters follow the type-naming convention for reference types such as:

public static int InsertCarpool(Carpool carpool)

I can either rename all of the parameters, change my public members to protected, or suppress the message. Both renaming the methods and changing the scope of the methods will require significant refactoring. I suppressed the method and made a vow that the next project, I will mark as CLS compliant first, so I enforce it right off of the bat.

My next error is a standard one that many developers blow off because they don’t think their application will be internationalized.

CA1305 : Microsoft.Globalization : Because the behavior of ‘DateTime.Parse(string)’ could vary based on the current user’s locale settings, replace this call in ‘CarPoolFactoryIntegrationTest.CreateTestObjects()’ with a call to ‘DateTime.Parse(string, IFormatProvider)’. If the result of ‘DateTime.Parse(string, IFormatProvider)’ will be based on input from the user, specify ‘CultureInfo.CurrentCulture’ as the ‘IFormatProvider’ parameter. Otherwise, if the result will based on input stored and accessed by software, such as when it is loaded from disk or from a database, specify ‘CultureInfo.InvariantCulture’.

Most of these errors occur in the creation methods like this:

Practice practice = PracticeFactory.CreatePractice(DateTime.Parse(testCarpoolStartDate), false, DateTime.Parse(testCarpoolStartTime), DateTime.Parse(testCarpoolEndTime), swimGroup);

So the biggest problem in refactoring is adding CultureInfo.InvariantCulture like this:

Practice practice = PracticeFactory.CreatePractice(DateTime.Parse(testCarpoolStartDate, CultureInfo.InvariantCulture),false, DateTime.Parse(testCarpoolStartTime, CultureInfo.InvariantCulture), DateTime.Parse(testCarpoolEndTime, CultureInfo.InvariantCulture), swimGroup);

Not the worst thing, so I refactored and ran my unit tests.

Another set of errors were spelling errors. “Carpool” not “CarPool”, “Email” not “EMail”, and “Integration” not “Intigration”. I renamed and ran my unit tests. Finally, I did add a resource file for Tff. That is the correct spelling so I added a custom dictionary to the project and marked it as “Code Analysis Dictionary” under its build action property

<?xml version="1.0" encoding="utf-8" ?> <Dictionary> <Words> <Recognized> <Word>Tff</Word> </Recognized> </Words> </Dictionary>

Next, Code Analysis detected that I was being a lazy programmer.

CA1062 : Microsoft.Design : In externally visible method ‘CarpoolFactory.InsertCarpool(Carpool)’, validate parameter ‘carpool’ before using it.

I did not validate parameters in a couple places in my solution. I went back and validated them, ran my unit tests, and moved on.

Finally, I got this kind of errors:

CA1801 : Microsoft.Usage : Parameter ‘carpoolSummary’ of ‘CarpoolSummaryFactory.UpdateCarpoolSummary(CarpoolSummary)’ is never used. Remove the parameter or use it in the method body.

How cool is code analysis? I removed the offending blocks of code.

Refactoring using FxCop was a great exercise and I am better developer because of it. I also realized that I need to run code analysis more frequently when I am coding to keep the error list down to a manageable level.

Test Cleanup Errors

I spent some time refactoring some unit tests in a VS2010 project.  One of the changes I made was to move some variables to class-level and initialize them in the class startup in a couple integration tests.

 

[ClassInitialize()] public static void MyClassInitialize(TestContext testContext) { CreateTestObjects(); } private static void CreateTestObjects() { SwimGroup swimGroup = SwimGroupFactory.GetSwimGroup(testSwimGroupId); Practice practice = PracticeFactory.CreatePractice(DateTime.Parse(testCarpoolStartDate), false, DateTime.Parse(testCarpoolStartTime), DateTime.Parse(testCarpoolEndTime), swimGroup); PracticeFactory.InsertPractice(practice); testPracticeId = practice.Id; Driver driver = DriverFactory.GetDriver(testDriverId); Swimmer swimmer = SwimmerFactory.GetSwimmer(testSwimmerId); Carpool carpool = CarpoolFactory.CreateCarpool(practice, driver); carpool.Swimmers.Add(swimmer); CarpoolFactory.InsertCarpool(carpool); testCarpoolId = carpool.Id; }

 

Note that this is a regression test – I actually want to test the database connection – so I am not using a fake.

I also run a clean-up method to remove the test data that I injected into the dev database.

private static void DestroyTestObjects() { Carpool carpool = CarpoolFactory.GetCarpool(testCarpoolId); CarpoolFactory.DeleteCarpool(carpool); Practice practice = PracticeFactory.GetPractice(testPracticeId); PracticeFactory.DeletePractice(practice); }

 

I noticed two important things:

1) The Cleanup runs even if there is an error thrown in a test

 

image

 

2) If the Cleanup throws an exception, all of the tests still pass.

 

So the tests run in separate thread than the test controller.  Very cool – but makes me realize that unit testing multi-threaded apps must be a bear…