Monday, November 2, 2009

Workflow foundation 4.0 part 4

It’s taken a while to get to this point but based on the changes in beta 2 of Visual Studio 2010 and the .Net Framework 4.0 it was probably prudent to wait.

There are a number of changes in beta 2, for one the designer has been revised again. Also there is only a single workflow. To create the the different workflows, sequence or flow chart, you start by dragging the appropriate activity container onto the design surface. The tool box has also been reorganized. The different components are organized by functionality.

In this post, I’m going to build a simple console workflow to read RSS feeds, in future posts I’ll enable WCF communications and workflow persistence. This workflow will read any number of feeds passed in as a workflow argument and returns the results from the workflow instance to the console.

To get started, open Visual Studio and create a new Workflow console application named RSSReaderWorkflow. You can change the name of the workflow to anything you like or leave it as is. I renamed the workflow to ReaderWorkflow for this project. One thing to note, you may have to open the workflow in xml mode to change the class name. The designer seems to pass this by when you rename from the solution explorer.



Now we need to a couple of container classes. The first, RSSFeeds contains a list of feed uri’s. This is the list of feeds read.

using System;
using System.Collections.Generic;


namespace RSSReaderWorkflow
{
public class RSSFeeds
{
public List<Uri> Feeds { get; set; }
}
}
 
 The second class we need to add is the RSSItem class. This class will contain the news items from the rss feeds that we read.

 
using System;

namespace RSSReaderWorkflow
{
public class RSSItem
{
public string Title { get; set; }

public string Link { get; set; }

public string Description { get; set; }
}
}




With that out of the way, drag a sequence activity from Control Flow section of the tool box. This is going to be a simple sequence workflow, so this activity serves as the container for the other activities we’ll add.


Next we’ll add two arguments one of the input named RssFeeds which is a type of RSSFeeds and is required.



The second argument is an out argument and will be a list of RSSItem. This will contain our feed results which we will display to the console.





Next drag an If activity from the tool box, we’ll add a condition to check that we have at least one feed to read.





Next in the else side of the if activity, we’ll add a throw activity. This activity will throw an argument out of range exception if the feeds count is zero.





Next we need to add a new code activity to read the feeds an populate the results. From the solution explorer right click –> add new item and select workflow and Code activity. Since we want to return the results of the rss feeds we’ll need to derive from TResult and add a bit of code. Here is the ReadFeedActivity class.






using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Xml.Linq;

namespace RSSReaderWorkflow
{

public sealed class ReadFeedActivity : CodeActivity<List<RSSItem>>
{
// Define an activity input argument of type string
public InArgument<List<Uri>> FeedUri { get; set; }


// If your activity returns a value, derive from CodeActivity<TResult>
// and return the value from the Execute method.
protected override List<RSSItem> Execute(CodeActivityContext context)
{
List<RSSItem> results = new List<RSSItem>();

foreach (var f in FeedUri.Get(context))
{
XDocument doc = XDocument.Load(f.ToString(), LoadOptions.None);
results.AddRange( (from i in doc.Descendants("channel").Elements("item")
select new RSSItem()
{
Title = i.Element("title").Value,
Link = i.Element("link").Value,
Description = i.Element("description").Value
}).ToList());
}
// Obtain the runtime value of the Text input argument
return results;
}
}
}
 
Now Build the project and the new activity will be avaliable in the tool box. You can simply drag it to the if activity.


 

 Set the FeedUri Property to the RssFeeds.Feeds argument property.







Set the Result to the Results output argument property.



Now we need to update the main. It will create and pass in the the feeds and display the results.


class Program
{
static void Main(string[] args)
{
// set up a list of input feeds
Dictionary<string,object> wfArgs = new Dictionary<string,object>();

// the workflow is expecting a feed list object
RSSFeeds list = new RSSFeeds()
{
Feeds = new List<Uri>()
{
//usa today
new Uri("http://rssfeeds.usatoday.com/usatoday-NewsTopStories"),
//cnn
new Uri("http://rss.cnn.com/rss/cnn_topstories.rss")
}
};
// add the input to the args dictionary
wfArgs.Add("RssFeeds",list);


try
{
// the workflow will return a dictionary with the rss items
IDictionary<string, object> wfResults = WorkflowInvoker.Invoke(new ReaderWorkflow(), wfArgs);

// loop through the keys
foreach (var item in wfResults)
{
// cast the value as an RSSItem list
List<RSSItem> res = item.Value as List<RSSItem>;

// display the results
res.ForEach(i => Console.WriteLine(i.Title));

}
}
catch(ArgumentOutOfRangeException e) // if you comment out the feeds, you can watch the throw in acction
{
Console.WriteLine(e.Message);
}

Console.Read();
}
}



Now we have a simple workflow that reads RSS feeds.