MVC, LINQ, and WinHost

I love MVC. I recently had to make some changes to the swim team website to get ready for the 2011 season. Because there was a nice SOC enforced by MVC, I simply had to add a couple of new controllers with some basic CRUD:

clip_image001

add their associated views, and then wire up to my factory using my POCOs:

public ActionResult Index(int? page) { FamilyFactory familyFactory = new FamilyFactory(); IQueryable<Family> families = familyFactory.GetAllFamilies() .OrderBy(family => family.FamilyDesc) .AsQueryable <Family>(); const int pageSize = 10; var paginatedFamilies = new PaginatedList<Family>(families, page ?? 0, pageSize); return View(paginatedFamilies); }

(note that I used the paginated list from Nerd Dinner)

Boom goes the dynamite.

Upon reflection, you can really see how useful using POCOs are when you have to extend your application. That upfront cost 2 years ago is paying huge dividends now.

I did run into 1 small hiccup when I deployed to WinHost. I received the following error:

clip_image002

After some digging (this error does not Google well), I added this:

<trust level="Full" />

To System.Web and things corrected.

MCPD 4.0

I passed exam 70-521 last week.

 

image

  I found the exam a bit different than other Microsoft exams that I have taken.  The content of the questions were pretty straightforward (except some of the LINQ questions were a bit contrived).  The biggest difference to prior exams is that you can apply test-taking strategies because on 95% of the questions, the answer was provided.  There were few, if any “none of the above” as answers.  That means it was fairly easy to use process of elimination  to narrow down the potential answers.

 

In other news, I created a slide deck to play before the TriNug meeting tomorrow night.  It is like the advertisements that play at some movies before the previews.  I took most of the questions from the MSFT training kit – though I threw in 1 question that I remember vividly from the exam.  I couldn’t remember it exactly, but the intent is like this:

 

What code sample makes this window in WPF (line numbers are for reference only)?

image

A:

  1. <DockPanel>            
  2. <Button Content="Top" DockPanel.Dock="Top"/>            
  3. <Button Content="Bottom" DockPanel.Dock="Bottom"/>            
  4. <Button Content="Left" DockPanel.Dock="Left"/>            
  5. <Button Content="Right" DockPanel.Dock="Right"/>           
  6. <Button Content="Center"/>        
  7. </DockPanel>

B:

  1. <DockPanel>            
  2. <Button Content="Top" DockPanel.Dock="Top"/>            
  3. <Button Content="Bottom" DockPanel.Dock="Bottom"/>            
  4. <Button Content="Left" DockPanel.Dock="Left"/>            
  5. <Button Content="Center"/>      
  6. <Button Content="Right" DockPanel.Dock="Right"/>             
  7. </DockPanel>

C:

  1. <DockPanel>            
  2. <Button Content="Left" DockPanel.Dock="Left"/>
  3. <Button Content="Top" DockPanel.Dock="Top"/>   
  4. <Button Content="Center"/>         
  5. <Button Content="Bottom" DockPanel.Dock="Bottom"/>            
  6. <Button Content="Right" DockPanel.Dock="Right"/>           
  7. </DockPanel>

D:

  1. <DockPanel>            
  2. <Button Content="Top" DockPanel.Dock="Top"/>            
  3. <Button Content="Bottom" DockPanel.Dock="Bottom"/>            
  4. <Button Content="Left" DockPanel.Dock="Left"/>            
  5. <Button Content="Right" DockPanel.Dock="Right"/>           
  6. <Button Content="Center“DockPanel.Dock
  7. </DockPanel>

It’s easy enough to figure out the answer – just copy past each possibility into a WPF form in VS2010.

In any event, I am going on vacation next week so I won’t post back here for 2 weeks.

VS2010 Parallel Debugger

I just worked through the Parallel Debugger Lab from Microsoft. Holy cow it is awesome.  You can set a break and immediate see all of the threads running with their call stack:

image

You can then drill into each thread (or set of threads you are interested in) and see the associated tasks:

image

And coolest of all, you can see each thread that is deadlocked and where the deadlock is coming from:

image

My only grip has nothing to do with the parallel debugger, it has to do with the poo quality of code in the lab.  Take a look at this code:

image

Horrible naming, unchecked arguments, single-line for multiple commands, weak-typing, uhg!!!!

Improvements Parallelism Can Make On The Random Service

Now that I have the Random Service set up and I have gone through the parallel extensions lab, I thought I could apply what I learned about parallelism to the random generator.  I first needed a way of measuring the time functions take and then breaking down the components of the functions into parts that can benefit from parallelism and those parts that can’t.  Also, I am curious to see how my local machine compares to my provider in terms of the benefits of parallelism.  My big assumption is that the number of records created by the random factory is fairly small – samples of 100, 200 and the like.

To that end, I created a quick performance test harness.  I started with the phone number generation because it was very straight forward:

        static void GeneratePhoneNumbersPerformanceTestLocalMachine()
        {
            int numberOfRecords = 1000;
            RandomFactory randomFactory = new RandomFactory();
            Stopwatch stopwatch = Stopwatch.StartNew();
            randomFactory.GetPhoneNumbers(numberOfRecords);
            stopwatch.Stop();
            Trace.WriteLine(String.Format("{0} phone numbers were generated in {1} seconds on the local machine",numberOfRecords, stopwatch.Elapsed.TotalSeconds));
        }

 

The problem my 1st attempt is pretty clear:

clip_image002[4]

Parallelism might help – but I am really not concerned about improving on thousandths of a second. 

I then added the GetDate() and Get UniqueIds() functions – because these methods do not hit the database or walk a large dataset:

 Starting performance test at 1/31/2011 8:04:32 AM

1000 phone numbers were generated in 0.0022865 seconds on the local machine

1000 dates were generated in 0.0008908 seconds on the local machine

1000 unique ids were generated in 0.000591 seconds on the local machine

Ending performance test at 1/31/2011 8:04:32 AM

I then decided to test GetLastName() using a 25%, 50%, 75%, and 100% prevalence thresholds (lower prevalence means fewer records to fetch and walk):

Starting performance test at 1/31/2011 8:01:36 AM

1000 phone numbers were generated in 0.0023858 seconds on the local machine

1000 dates were generated in 0.0006502 seconds on the local machine

1000 unique ids were generated in 0.0006484 seconds on the local machine

1000 last names (25% prevalence) were generated in 1.2553884 seconds on the local machine

1000 last names (50% prevalence) were generated in 0.3628737 seconds on the local machine

1000 last names (75% prevalence) were generated in 1.3719554 seconds on the local machine

1000 last names (100% prevalence) were generated in 8.9350157 seconds on the local machine

Ending performance test at 1/31/2011 8:01:48 AM

Interestingly, it looks like the connection is being re-used, so the 50% is faster than the 25%.  Note the spike at 100% though – perhaps Parallelism might help there.  I finished my testing suite for GetFirstNames(), GetAddresses(), GetPeople(), GetEmployees().  Here is the final results:

Starting performance test at 1/31/2011 9:28:36 AM

1000 phone numbers were generated in 0.0022125 seconds on the local machine

1000 dates were generated in 0.000647 seconds on the local machine

1000 unique ids were generated in 0.0006895 seconds on the local machine

1000 last names (25% prevalence) were generated in 1.4208552 seconds on the local machine

1000 last names (50% prevalence) were generated in 0.3804186 seconds on the local machine

1000 last names (75% prevalence) were generated in 1.4271377 seconds on the local machine

1000 last names (100% prevalence) were generated in 11.5619451 seconds on the local machine

1000 male first names (25% prevalence) were generated in 0.126765 seconds on the local machine

1000 male first names (50% prevalence) were generated in 0.0956216 seconds on the local machine

1000 male first names (75% prevalence) were generated in 0.1013383 seconds on the local machine

1000 male first names (100% prevalence) were generated in 0.2053033 seconds on the local machine

1000 female first names (25% prevalence) were generated in 0.1130885 seconds on the local machine

1000 female first names (50% prevalence) were generated in 0.0998854 seconds on the local machine

1000 female first names (75% prevalence) were generated in 0.1070964 seconds on the local machine

1000 female first names (100% prevalence) were generated in 0.9740046 seconds on the local machine

1000 both first names (25% prevalence) were generated in 0.1893091 seconds on the local machine

1000 both first names (50% prevalence) were generated in 0.2349195 seconds on the local machine

1000 both first names (75% prevalence) were generated in 0.2078913 seconds on the local machine

1000 both first names (100% prevalence) were generated in 1.1015048 seconds on the local machine

1000 street addresses were generated in 12.6074157 seconds on the local machine

1000 people (100% prevalence, both genders) were generated in 28.0779342 seconds on the local machine

1000 employees (100% prevalence, both genders) were generated in 29.1355036 seconds on the local machine

Ending performance test at 1/31/2011 9:30:05 AM

 

So, now it’s time to parallelize.  Taking the path of biggest bang for the CPU cycle, I decided to looks at last names and street addresses.  Diving into the code, I changed this:

            for (int i = 0; i < numberOfNames; i++)
            {
                randomIndex = random.Next(1, lastNameQuery.Count);
                selectedLastName = lastNameQuery[randomIndex-1];
                lastNames.Add(selectedLastName.LastName);

To this:

            Parallel.For(0, numberOfNames, (index) =>
            {
                randomIndex = random.Next(1, lastNameQuery.Count);
                selectedLastName = lastNameQuery[randomIndex – 1];
                lastNames.Add(selectedLastName.LastName);

And the output was:

1000 last names (100% prevalence) were generated in 11.8207654 seconds on the local machine

It went up!  OK, so the performance hit is not on the for loop – it is the fetching of the records from the database. 

I changed this code

            List<string> lastNames = new List<string>();
            var context = new Tff.Random.tffEntities();
            List<Census_LastName> lastNameQuery =

             (from lastName in context.Census_LastName
             where lastName.CumlFrequency < pervalence
             select lastName).ToList<Census_LastName>();

To this:

            List<string> lastNames = new List<string>();
            var context = new Tff.Random.tffEntities();
            List<Census_LastName> lastNameQuery =

              (from lastName in context.Census_LastName
              .AsParallel()
              where lastName.CumlFrequency < pervalence
              select lastName).ToList<Census_LastName>();

And the output was:

1000 last names (100% prevalence) were generated in 17.2297868 seconds on the local machine

Wow, I wonder if each thread is creating its own database connection?  I fired up SQL Performance monitor against a local machine instance of my database (my provider does not let me have sysadmin on the database – ahh some rain in those clouds).  No surprise that when I moved the database to my local machine, performance improved dramatically:

1000 last names (100% prevalence) were generated in 2.1497559 seconds on the local machine

In any event, I slapped on SQL Performance Monitor.  It looks like there is only 1 call being made (I expected 4 for my quad processor):

clip_image003[4]

 It looks like it I want to speed things up, I need to speed up the database call and PLINQ can’t help there.  The best way would be to take the entire datasets and cache them then memory when the application starts up.

I will confirm these ideas using some of the lab tools with VS2010 in the coming weeks…

 

Parallelism Labs From Microsoft

 

I did the PLINQ Lab this morning.  The lab itself is fairly short and givers a great overview of both the power of Parallelism and the ease of use in C#.  In addition, the last exercise shows how use Extension Methods on your IEnermable sources to further manipulate the data.  My only gripe is that the VM screen real estate is very small:

image

And you can’t change the resolution on the VM desktop to see more of the code.  The other gripe I have (only) is that the performance on the VM stinks – you literally wait 1-2 seconds after typing a character to see the intellisense to come up.  This kind of context delay makes it harder to retain the information in the lab.

I then started the Introducing .NET4 Parallel Extensions lab.  The screen delays were even worse so I took matters into my own hands.  I took some screen shots of the lab created a local project based on the starting solution.  One of the 1st tasks was to create a set of 20 random Employees.  Instead of hard-coding values into the list, and limiting the list to only 20 employees, I decided to create a random employee generator as a WCF Service.  That is the subject of this blog post

I had fun recreating the lab.  I then went through each exercise.  It did a good job explaining each of the aspects of Parallelism syntax.  I have 1 note and 1 gripe.  The note is that in the PLINQ,  you can see how the TaskManager split the dataset in two process  1 took the 1st 50% and process 2 took the last 50%.  Presumably, if I had a quad machine, it would be divided into four:

image

My 1 gripe has to do with the overuse of the ‘var’ keyword and the use of unreadable code in a public project.  Take a swing though this syntax:

            var q = employeeList.AsParallel()
                .Where(x => x.Id % 2 == 0)
                .OrderBy(x => x.Id)
                .Select(x => PayrollServices.GetEmployeeInfo(x))
                .ToList();
            foreach (var e in q)
            {
                Console.WriteLine(e);
            }

 

foreach(var e in q)??? Ugh!  A little more thought about variable names (q should be employeeListQuery, x should be employee, e should also be employee).  Oh well, the struggle continues…

Big (Random) Generator

I needed to create a random value generator for working on the Parallel Extension Labs that I blogged about here.  The class that the lab has is pretty straight forward:

    public class Employee
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public DateTime HireDate { get; set; }
 
        public override string ToString()
        {
            return string.Format("Employee{0}: {1} {2} Hired:{3}", Id, FirstName, LastName, HireDate.ToShortDateString());
        }
    }

 

(I added the ToString() as a convience).  I decided that I would create a WCF Service to provide the data – primarily because I haven’t worked with WCF in 4.0 at all.

So, I created a WCF Service Application, added it to Source Control with my typical 3 branching strategy, and then published it to provider.  Everything deployed correctly so I dug into the actual service.

image

The Service returns native .NET types (Strings, DateTimes, and Guids) as well a Person and Employee classes:

image

Each of the values need to be random yet close enough to be plausible.  I started with Phone Number:

        public List<string> GetPhoneNumbers(int numberOfPhoneNumbers)
        {
            List<string> phoneNumbers = new List<string>();
            System.Random random = new System.Random();
            int  areaCodeNumber = 0;
            int prefixNumber = 0;
            int suffixNumber = 0;
 
            for (int i = 0; i < numberOfPhoneNumbers; i++)
            {
                areaCodeNumber = random.Next(100,999);
                prefixNumber =  random.Next(100,999);
                suffixNumber = random.Next(1000,9999);
                phoneNumbers.Add(String.Format("{0}-{1}-{2}"
                    areaCodeNumber, prefixNumber, suffixNumber));
            }
 
            return phoneNumbers;

And for the singular:

        public string GetPhoneNumber()
        {
            return GetPhoneNumbers(1).First();

I used this Collection/Singular pattern throughout the service.  In addition, I implemented the singular consistently: create the plural and then take the first.

I then added some Unit Tests for each of my methods:

        [TestMethod()]
        public void GetPhoneNumberTest()
        {
            string notExpected = string.Empty;
            string actual = randomFactory.GetPhoneNumber();
            Assert.AreNotEqual(notExpected, actual);
        }
 
        [TestMethod()]
        public void GetPhoneNumbersTest()
        {
            int expected = 3;
            int actual = randomFactory.GetPhoneNumbers(3).Count;
            Assert.AreEqual(expected, actual);

 

This pattern of testing was also applied consistently across all of the methods.

Once I had the easy mathods done (Get Phone Number, Get Dates, etc..),  I tackled the methods that required external data.  To generate random names, I started with the US Census where I downloaded the first and last names into an MSAccess database.  I then turned around and put the data into a SQL Server database on WinHost.  BTW: I ran into this problem, took me 30 minutes to figure it out).  Once the data was in the database, I could fire up EF:

image

The data is composed of actual names, the Frequency that they appear in America, the Cumulative Frequency that each name contains, and the rank of popularity:

image

(OT: my daughter wrote this:

image

Who knew?)

Anyway, I then created a method that pulls the records from database below the prevalence of the name and then returns a certain number of the records randomally:

        public List<string> GetLastNames(int numberOfNames, int pervalence)
        {
            if (pervalence > 100 || pervalence < 0)
                throw new ArgumentOutOfRangeException("’Pervalence’ needs to be between 0 and 100.");
 
            List<string> lastNames = new List<string>();
            var context = new Tff.Random.tffEntities();
            List<Census_LastName> lastNameQuery = (from lastName in context.Census_LastName
                                                   where lastName.CumlFrequency < pervalence
                                                   select lastName).ToList<Census_LastName>();
            System.Random random = new System.Random();
            int randomIndex = 0;
            Census_LastName selectedLastName = null;
 
            for (int i = 0; i < numberOfNames; i++)
            {
                randomIndex = random.Next(1, lastNameQuery.Count);
                selectedLastName = lastNameQuery[randomIndex-1];
                lastNames.Add(selectedLastName.LastName);
            }
 
            return lastNames;

I am not happy with this implementation – I will add Parallelism to this to speed up the processing later – and I might implement a .Random() extension method to the LINQ.  In any event, the data came back and my unit tests passed.  I then implemented a similar method for the male and female first names.

With the names out of the way, I need to figure out street addresses.  I first thought about using Google’s reverse GPS mapping API and throwing in random GPS coordinates like this:

                string uri = @"http://maps.googleapis.com/maps/api/geocode/xml?latlng=40.714224,-73.961452&sensor=false&quot;;
                WebRequest request = WebRequest.Create(uri);
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                Stream dataStream = response.GetResponseStream();
                StreamReader reader = new StreamReader(dataStream);
                string responseFromServer = reader.ReadToEnd();
                Console.WriteLine(responseFromServer);
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.LoadXml(responseFromServer);
                XmlNodeList xmlNodeList = xmlDocument.GetElementsByTagName("formatted_address");
                string address = xmlNodeList[0].InnerText;
                reader.Close();
                dataStream.Close();
                response.Close();

 

The problem is that I don’t know exact coordinates so I would have to keep generating random ones until I got a hit – which means I would limit my search to a major metro area (doing this in a low-density state would mean many,many requests to find an actual street address).  Also, I would have the danger of actually using a real address.  Finally, Google limtis the number of requests per day, so I would be throttled – esp with a shotgun approach.

Instead, I went back to the census and found a data table with lots (not all) zip codes, cities, and states.  I then realized all I had to do was create a fake street number – easy enough, a fake street name using the last name table, and a random zip code.  Volia: a plausible yet random address.

Here is the EF Class:

image

And here is the code (split across 3 functions):

        public List<string> GetStreetAddresses(int numberOfAddresses)
        {
            List<string> streetAddresses = new List<string>();
            List<string> streetNames = GetLastNames(numberOfAddresses, 100);
            List<string> streetSuffixs = GetRandomStreetSuffixs(numberOfAddresses);
            List<string> zipCodes = GetZipCodes(numberOfAddresses);
 
            string streetNumber = string.Empty;
 
            System.Random random = new System.Random();
 
            for (int i = 0; i < numberOfAddresses; i++)
            {
                streetNumber = random.Next(10, 999).ToString();
                streetAddresses.Add(String.Format("{0} {1} {2} {3}", streetNumber, streetNames[i], streetSuffixs[i], zipCodes[i]));
            }
 
            return streetAddresses;

And:

private List<string> GetZipCodes(int numberOfZipCodes)
        {
            List<string> zipCodes = new List<string>();
            var context = new Tff.Random.tffEntities();
            List<Census_ZipCode> zipCodeQuery = (from zipCode in context.Census_ZipCode
                                                 select zipCode).ToList<Census_ZipCode>();
            System.Random random = new System.Random();
            int randomIndex = 0;
            Census_ZipCode selectZipCode = null;
 
            for (int i = 0; i < numberOfZipCodes; i++)
            {
                randomIndex = random.Next(1, zipCodeQuery.Count);
                selectZipCode = zipCodeQuery[randomIndex-1];
                zipCodes.Add(String.Format("{0}, {1} {2}", selectZipCode.City, selectZipCode.StateAbbreviation, selectZipCode.ZipCode));
            }
 
            return zipCodes;

Finally:

        private List<string> GetRandomStreetSuffixs(int numberOfSuffixes)
        {
            List<String> suffixes = new List<string>();
            List<string> returnValue = new List<string>();
            suffixes.Add("STREET");
            suffixes.Add("ROAD");
            suffixes.Add("DRIVE");
            suffixes.Add("WAY");
            suffixes.Add("CIRCLE");
 
 
            System.Random random = new System.Random();
            int randomIndex = 0;
            for(int i=0; i < numberOfSuffixes; i++)
            {
                randomIndex = random.Next(1,suffixes.Count);
                returnValue.Add(suffixes[randomIndex-1]);
            }
 
            return returnValue;

 

Now, when you hit the service, you can get a plausible yet totally fake dataset of people and employees:

            Random.RandomFactoryClient client = new Random.RandomFactoryClient();
            List<Random.Employee> employees = client.GetEmployees(20, 50, Random.Gender.Both, 10);
 
            for (int i = 0; i < employees.Count; i++)
            {
                Add(new Employee
                {
                    Id = i,
                    FirstName = employees[i].FirstName,
                    LastName = employees[i].LastName,
                    HireDate = employees[i].HireDate,
                    Address = employees[i].StreetAddress
                });

Spit out to the Console:

image

In case you want to use the service, you can find it here.

VERY IMPORTANT: I set the return values to be of type List<T>.  I know this is considered bad practice from an interoperability standpoint.  If you are using VS2010 and you want to consume the service, make sure you do this when you attach to the reference:

image

Results may vary.

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.

FTP and WF in TFS

Following the creation of my FTP WF tasks that I created here, I then went to add those tasks into the TFS Workflow.  I already have created a special template and found where I want to add the tasks here so it was only a matter of dropping the tasks into the WF, right?  Wrong.  The WF templates are not part of any given project, so you can’t just create WF tasks in a project and reference them in the template.  You need to add them to the toolbar and to do that, you need to create the tasks in an independent project.  I first tried to right click on the toolbox and add a reference to the project that I just created, however when I went to drag…drop the task into the designer, I got the red “no” symbol.  After some research, I found this post – I have to add these activities to the GAC.  After strong naming the original project and using GACUtil, I then could navigate to C:\Windows\Microsoft.NET\assembly\GAC_MSIL (not C:\Windows\assembly btw) and bring the FTP tasks in.  Interestingly, the top-level task (the sequence) did not “bubble up” the child in and out parameters so I had to drag the 3 actual code activities from the designer and place them into the TFS WF.

image

Once I did that, I need to hook up the build directory to the source for the FTP Copy Directory task.  I didn’t try to alter the path at all, so I had to use the fully-qualified path as so:

image

This corresponds to the file system like this:

image

Note that I am only copying the website (the same way VS2010 does it) and I am not copying the source code or the debugging symbols.

After completing that task, I then checked the project into source control (Development to Main to Release). The build kicked off and the files moved up to the host as expected:

image

Cow-A-Bunga!

I would like to then refactor the WF activities to take all of the code behind and put them on the designer, but that is next week – once I implement these FTP activities to all of the websites that I maintain.

FTP and WF: Setting the Stage

Following up on my post here regarding TFS and WF, I went to write the actual WF tasks that correspond to the FTP activities of moving files over to a remote host.  I crated 3 tasks

The first connects to the FTP Host:

public sealed class ConnectToHost : CodeActivity { FTPConnection _ftpConnnection = null; public InArgument<string> HostName { get; set; } public InArgument<string> UserName { get; set; } public InArgument<string> Password { get; set; } public InOutArgument<FTPConnection> FTPConnection { get; set; } public ConnectToHost() { _ftpConnnection = new FTPConnection(); } public FTPConnection ActiveConnection { get { return _ftpConnnection; } } protected override void Execute(CodeActivityContext context) { _ftpConnnection.ServerAddress = HostName.Get(context); _ftpConnnection.UserName = UserName.Get(context); _ftpConnnection.Password = Password.Get(context); _ftpConnnection.Connect(); } }

I then created a Disconnect Task:

public sealed class DisconnectFromHost : CodeActivity { FTPConnection ftpConnection = null; public DisconnectFromHost() { } public DisconnectFromHost(FTPConnection ftpConnection) { this.ftpConnection = ftpConnection; } protected override void Execute(CodeActivityContext context) { ftpConnection.Close(); } }

And finally created the Task that does the actual copying:

public sealed class CopyDirectoryContents : CodeActivity { FTPConnection ftpConnection = null; public CopyDirectoryContents() { } public CopyDirectoryContents (FTPConnection ftpConnection) { this.ftpConnection = ftpConnection; } public InArgument<string> SourceDirectoryName { get; set; } public InArgument<string> TargetDirectoryName { get; set; } protected override void Execute(CodeActivityContext context) { MoveDirectoryContents(SourceDirectoryName.Get(context), TargetDirectoryName.Get(context)); } public void MoveDirectoryContents(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); MoveDirectoryContents(subDirectoryInfo.FullName, subDirectoryInfo.Name); } ftpConnection.ChangeWorkingDirectoryUp(); } }

I then wired them up in a sequence activity and added a variable to hold the FTPConnection that will be created by the 1st task and then passed to the second and third task:

image

I then assigned values to the ConnectToHost parameters:

image

Note that FTPConnection is an InOutArgument so I can assign it from the higher level sequence.

I then added the arguments to the CopySite Task. Note that the designer handles the slashes (I couldn’t use the @”” syntax):

image

I got an error when I realized I don’t need to use the constructors, rather I need to use the In and OutParameters like so:

1) Create the ftpConnection and pass it out:

FTPConnection _ftpConnnection = null; public InArgument<string> HostName { get; set; } public InArgument<string> UserName { get; set; } public InArgument<string> Password { get; set; } public OutArgument<FTPConnection> FTPConnection { get; set; } protected override void Execute(CodeActivityContext context) { _ftpConnnection = new FTPConnection(); _ftpConnnection.ServerAddress = HostName.Get(context); _ftpConnnection.UserName = UserName.Get(context); _ftpConnnection.Password = Password.Get(context); _ftpConnnection.Connect(); FTPConnection.Set(context, _ftpConnnection); } }

2) Store the FTPConnection in the SequenceActivity’s local variable.

3) Get the FTP as an InArgument and use it:

public InArgument<string> SourceDirectoryName { get; set; } public InArgument<string> TargetDirectoryName { get; set; } public InArgument<FTPConnection> FTPConnection { get; set; } protected override void Execute(CodeActivityContext context) { ftpConnection = FTPConnection.Get(context); MoveDirectoryContents(SourceDirectoryName.Get(context), TargetDirectoryName.Get(context)); }

And boom goes the dynamite! It worked and the files moved over. I can now set up the FTP tasks in the TFS Build Activity.

Multiple Entity Framework Models in the same project

Continuing my journey though Entity Frameworks 4.0 Recipes, I have 1 EF Model in a project (Example 2-2). I then added a new model (example 2-4) with a different namespace.

image

Everything works fine.

I then added a third model with a different namespace

image

When I try and compile I get a bunch of nasty errors:

image

I double checked the properties of each of the second and third model – note that the namespace properties are different:

image

image

Seeing that they each shared the same Entity Container Name, I changed them and then ran into this error:

image

I then found my answer – I re-used the connection string. Unfortunately, there is context-specific information in the connection string. When I copy/pasted the connection string:

1 <add name="ProductEntities" 2 connectionString="metadata=res://*/Poetry.csdl|res://*/Poetry.ssdl|res://*/Poetry.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.;Initial Catalog=EFRecipies;Integrated Security=True;MultipleActiveResultSets=True&quot;" 3 providerName="System.Data.EntityClient" />

I found that .csdl, etc… were still referencing the original context. Changing that to the new context (or re-generating the model using a different connection string) did the trick:

image