Recursive WF Activities

I want to start backing up my home machine hard drive. There are a bunch of services out there and some of them use size-based storage – the more space you use, the more they charge you. When you look in Windows Explorer, there is not an easy way to determine the size of a given directory – just file sizes:

clip_image001

Since I am not backing up the entire drive, I can’t look at the drive size and space used to determine how much a storage company will cost – so I can’t adequately price compare.

clip_image002

I then thought of whipping up a quick recursive function to get the cumulative size of all of the files in a directory. I then thought I could use the exercise to investigate recursion in WF.

I set up a test location on my file system (I know, I should be using a Mocking Framework):

clip_image004

I then wrote a function that gets the total size of all of the files in a directory (and any subdirectory) like this:

 

static long GetFileSize(string directoryName) { long cumulativeSize = 0; foreach (FileInfo fileInfo in new DirectoryInfo(directoryName).GetFiles()) { cumulativeSize += fileInfo.Length; } return cumulativeSize; }

I then wrote a unit (integration, really) test to verify the function as it is currently written:

[TestMethod()] [DeploymentItem("Tff.DirectorySize.exe")] public void GetFileSizeTest() { string directoryName = @"C:\TestDirectory"; long expected = 12647; long actual = Program_Accessor.GetFileSize(directoryName); Assert.AreEqual(expected, actual); }

I then added the recursion bit into the function:

static long GetFileSize(string directoryName) { long cumulativeSize = 0; DirectoryInfo directoryInfo = new DirectoryInfo(directoryName); foreach (FileInfo fileInfo in directoryInfo.GetFiles()) { cumulativeSize += fileInfo.Length; } foreach (DirectoryInfo subdirectoryInfo in directoryInfo.GetDirectories()) { cumulativeSize += GetFileSize(subdirectoryInfo.FullName); } return cumulativeSize; }

Still got green on my original unit test. I then added a new subdirectory to the test directory and copy/pasted the original test files into the test subdirectory. I changed the unit test to be like so:

[TestMethod()] [DeploymentItem("Tff.DirectorySize.exe")] public void GetFileSizeTest() { string directoryName = @"C:\TestDirectory"; long expected = 12647 * 2; long actual = Program_Accessor.GetFileSize(directoryName); Assert.AreEqual(expected, actual); }

I still got green. This is a great exercise to show the problems with mixing integration tests with unit tests and the usefulness of Mocking frameworks.

I then added a WF Activity to the project. Interestingly, I could not add a variable to this blank activity.

I added a Flowchart activity to the project and added a local variable called cumulativeSize. A pain-in-the-butt feature of WF is that long (and most other types) and not available in the select box.

clip_image006

When browsing, remember to look in MSCorLib and System Namespace

clip_image007

If you forgot that long is actually System.Int64

clip_image008, In

Intellisense can help you:

clip_image010

In any event, the 1st line of the function was replicated in WF:

clip_image012

I then realized I need to set up the input parameters of the function/flowchart. Easy Enough:

clip_image014

I then looked at the next line of the function:

I need a way of representing an instantiated DirectoryInfo object in my WF using that input argument. I realized that I need to wrap the DirectoryInfo class with an activity to use it. I dropped in a code activity thinking that all I had to do was implement the IDirecotryInfo interface. Note that to avoid name collisions, I called the activity “DirectoryInfoActivity”, not “DirectoryInfo”.

Alas, there is no such thing as a IDirectoryInfo interface. DirectoryInfo inherits from FileSystemInfo and is a sealed class. Ugh! If it was at least a partial class, I could reverse-engineer the interface (like I do using EF4), but that is out.

I then realized that I should just make the DirectoryInfo class a local variable for the workflow:

clip_image016

I could then do a For..Each activity on that local variable. A couple of key points:

You need to set the property of the ForEach Type to System.IO.FileInfo like so:

clip_image018

Just changing it in the Designer <FileInfo> doesn’t do anything.

Also, working in VB syntax in little text boxes can be very frustrating. Using the elipises in the property window is very helpful:

clip_image020

Next, I created a Unit Test (manually, a context menu with Create..UnitTest would be very helpful Microsoft). I had to lookup that the WorkflowInvoker is in System.Activities:

[TestMethod] public void GetFileSizeTest() { string directoryName = @"C:\TestDirectory"; long expected = 12647; GetFileSize getFileSize = new GetFileSize(); getFileSize.directoryName = directoryName; IDictionary<string, object> results = WorkflowInvoker.Invoke(getFileSize); long actual = Int64.Parse(results["cumulativeSize"].ToString()); Assert.AreEqual(expected, actual); }

The first time I ran it, I got a null reference exception -> apparently you have to New up your objects – WF doesn’t do that for you:

clip_image022

And then we get a pass:

clip_image023

The next challenge is to add in the recusion. I put in the next For..Each Loop:

clip_image024

clip_image025

But the problem is that the right-hand side needs to invoke the workflow again. I don’t see any InvokeWorkflow activies in the toolbox, so I made a code activity. This post is taking longer than I want, so I’ll drop in the remaining tomorrow.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: