3 result(s) were found in 0.026 milliseconds
A week ago I posted the first part of the Really Simple Sample, now I will present you the extended version of the sample. To begin with the sample is not a finished application, it is a sample to show you how to use the Microsoft Feeds API, so you will find some gaps in the code and some functionality that isn't implemented or could be implemented in a better way. If you like the concept feel free to use the code and complete the application. Let me now if you succeed in doing so.
This is how the sample looks like when you are running it.

There's a treeview containing the feeds of the Common Feed List, next to it there are the following buttons.
- "Export Folder": Export a folder to a xml file
- "Import Folder": Import a folder from a xml file
- "Refresh Feeds": Refresh the complete Common Feed List
- "Import From Feed": Import a folder from a xml file included in an enclosure of a feeditem.
The same functionality is included in a contextmenu. There is an additional "Download" menuitem that will download a single feed.
Building the treeview
The TreeView is builded with conventional recursive methods and custom derived classes from TreeNode to contain the actual Feed, FeedFolder and FeedItem interfaces.
private void FillTreeView() {
FeedTreeView.Nodes.Clear();
IFeedsManager manager = new FeedsManagerClass();
IFeedFolder rootFolder = (IFeedFolder)manager.RootFolder;
foreach (IFeedFolder subfolder in (IFeedsEnum)rootFolder.Subfolders) {
AddFeedFolderToTreeView(subfolder, FeedTreeView.Nodes);
}
foreach (IFeed feed in (IFeedsEnum)rootFolder.Feeds) {
AddFeedToTreeView(feed, FeedTreeView.Nodes);
}
}
private void AddFeedFolderToTreeView(IFeedFolder folder, TreeNodeCollection nodes) {
FeedFolderTreeNode node; node = new FeedFolderTreeNode(folder.name);
node.FeedFolder = folder; nodes.Add(node);
foreach (IFeedFolder subfolder in (IFeedsEnum)folder.Subfolders) {
AddFeedFolderToTreeView(subfolder, node.Nodes);
}
foreach (IFeed feed in (IFeedsEnum)folder.Feeds) {
AddFeedToTreeView(feed, node.Nodes);
}
}
private void AddFeedToTreeView(IFeed feed, TreeNodeCollection nodes) {
FeedTreeNode feedNode = new FeedTreeNode(feed.name);
feedNode.Feed = feed; nodes.Add(feedNode);
if (IsFeedFeedImport(feed)) {
AddFeedItemsToTreeView(feed, feedNode.Nodes);
}
}
private void AddFeedItemsToTreeView(IFeed feed, TreeNodeCollection nodes) {
foreach (IFeedItem item in (IFeedsEnum)feed.Items) {
if (item.Enclosure != null) {
IFeedEnclosure enclosure = (IFeedEnclosure)item.Enclosure;
if (enclosure.Type == "text/xml") {
FeedItemTreeNode feedItemNode = new FeedItemTreeNode(item.Title);
feedItemNode.FeedItem = item;
nodes.Add(feedItemNode);
}
}
}
}
The derived TreeNode classes.
public class FeedTreeNode : TreeNode {
public FeedTreeNode(string Text): base(Text) {
}
public IFeed Feed;
}
public class FeedFolderTreeNode : TreeNode {
public FeedFolderTreeNode(string Text): base(Text) {
}
public IFeedFolder FeedFolder;
}
public class FeedItemTreeNode : TreeNode {
public FeedItemTreeNode(string Text): base(Text) {
}
public IFeedItem FeedItem;
}
Checking if a feed is a Common Feed List Import
As an example to check for custom rss elements I check the feed for the "widec:cflimport" element, this is done with the Feed.Xml method. This method returns the actual xml of the feed as a string, it can then be used to create a XmlDocument. This way the application can test if a feed is a CFL import list, this excludes importing enclosures imports from regular rss feeds, if you want to import CFL imports from a regular rss feed you can only test if the enclosure type is "text/xml".
private bool IsFeedFeedImport(IFeed feed, bool quickCheck) {
if (feed.DownloadStatus != FEEDS_DOWNLOAD_STATUS.FDS_DOWNLOADED) {
//Always check for the downloadstatus, if the feed hasn't been downloaded you can't
//get the Xml source. A FileNotFound exception is thrown.
if (quickCheck) {
return false;
}
try {
feed.Download();
} catch {
return false;
}
}
// To get the xml data of the channel without any items, just fill in 0.
string xmlFeed = feed.Xml(0, FEEDS_XML_SORT_PROPERTY.FXSP_NONE, FEEDS_XML_SORT_DIRECTION.FXSD_NONE,
FEEDS_XML_FILTER_FLAGS.FXFF_ALLITEMS, FEEDS_XML_INCLUDE_FLAGS.FXIF_NONE);
XmlDocument document = new XmlDocument();
document.LoadXml(xmlFeed);
XmlNodeList nodes = document.GetElementsByTagName("widec:cflimport");
if (nodes.Count != 0) {
if (nodes.Item(0).InnerText == "regular") {
return true;
}
}
return false;
}
Downloading Enclosures
If we know a feed is a CFL import feed or a feeditem has an enclosure of the type "text/xml" we can import the xml file. The catch here is that the Enclosure is not always downloaded, when this happens we need to download it, this can be done with the Enclosure.AsyncDownload method. The method speaks for itself, we have to wait until the file is downloaded and I use the BackgroundWorker class for this. If you don't know how to work with this class check out the article "Background Processing in Windows Forms Applications (Part 2)" by Peter Himschoot. When the Enclosure is not downloaded I show a new form called EnclosureDownload, this form will download the Enclosure and show a progressbar. For importing the actual xml file I use the FeedImportExport class presented in a previous post.
private void ImportFromFeedButton_Click(object sender, EventArgs e) {
if (FeedTreeView.SelectedNode is FeedItemTreeNode){
IFeedItem item = (FeedTreeView.SelectedNode as FeedItemTreeNode).FeedItem;
if (item.Enclosure != null) {
IFeedEnclosure enclosure = (IFeedEnclosure)item.Enclosure;
if (enclosure.Type == "text/xml") {
if (enclosure.DownloadStatus != FEEDS_DOWNLOAD_STATUS.FDS_DOWNLOADED) {
EnclosureDownload downloader = new EnclosureDownload();
downloader.StartDownload(enclosure);
if (downloader.ShowDialog() == DialogResult.OK) {
FeedImports imports;
try {
imports = FeedImports.Deserialize(enclosure.LocalPath);
} catch (Exception ex) {
MessageBox.Show(ex.Message);
return;
}
FeedImportExport import = new FeedImportExport();
import.Import(imports);
FillTreeView();
}
}
}
}
}
}
The actual BackgroundWorker DoWork method checks for a complete download and if the download is queued. This is done by checking the Enclosure.DownloadStatus, the user can also cancel the download.
private void DownloadBackgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker;
IFeedEnclosure enclosure = e.Argument as IFeedEnclosure;
bool queued = false;
enclosure.AsyncDownload();
if (enclosure.DownloadStatus == FEEDS_DOWNLOAD_STATUS.FDS_PENDING) {
worker.ReportProgress(0, "Download is queued, click cancel to stop downloading.");
queued = true;
}
while ((enclosure.DownloadStatus == FEEDS_DOWNLOAD_STATUS.FDS_DOWNLOADING) ||
(enclosure.DownloadStatus == FEEDS_DOWNLOAD_STATUS.FDS_PENDING)) {
System.Threading.Thread.Sleep(100);
if (worker.CancellationPending) {
enclosure.CancelAsyncDownload();
}
if (queued){
if (enclosure.DownloadStatus == FEEDS_DOWNLOAD_STATUS.FDS_DOWNLOADING){
queued = false;
worker.ReportProgress(10, "Downloading started");
}
}
}
e.Result = enclosure.DownloadStatus;
}
Conclusion
This technology has endless possibilities, it is cool stuff. The API is now only available in COM but there will be a managed version no doubt about it. There are some missing events like when a download of an enclosure is finished but hey it is beta stuff.
The complete source code is available.
For more information check out the following links
You can build the sample on XPSP2 with IE7 Beta 2 or on Vista CTP January or CTP february. To test the enclosure import you should build a rss feed with enclosures, I used VS2003 and returned a fixed rss from an aspx page, this page is also included with the source code.
I know I promised an updated version of the really simple sample, but I got hooked on the Simple List Extensions. So first I'm going to give a small sample on SLE combined with the Microsoft Feeds API. In our company we develop call center solutions and a small part of the application is a scoreboard, this scoreboard gives the scores of all projects and agents, how good is a project and what is an agent scoring. These scores are calculated by an application and every project as a custom algorithm, some of the calculations take minutes to finish since historical data is sometimes needed. At this moment this application stores the scores in a sql table, another application the scoreboard loops through the table and showes the scores in a graphical manner. This way you can start multiple scoreboards without more load on the sql databases. At this time managers asked too see the the scores at home, "get it online" as they say. Well this is where SLE comes in play, generate an rss feed from the calculator application and your done, the scoreboard application only uses the rss feeds as source. No impact on the sql server and rss is readable without a custom application from any location.
Great, the following sample is a prove of concept and I wanted to share this with you all.
First of all we need a feed, just put an xml file on a webfolder and you have a feed. the following is an example of such an xml file.
It looks like a regular rss file but we added the cf:treatAs element, this element informs the Rss Platform Engine that this rss stream is a list. More over we can add additional sorting with the cf:sort element. The rss feed now looks like this in IE7, we see the sorting in action through the "The Agent" and "The Score" buttons.

Of course this is all great, but what if we wanted to use this feed in a custom application. As an example I will show you how to load the items in a listbox, and what is more the listbox will be updated every time the feed is downloaded. The application contains two major methods "BindFeedEvents" and "UpdateScoreBoard". First of all we are going to look at the UpdateScoreBoard method, this method loads the feed and gets the items of the feed, once it has an item it reads the score and agent elements from the scoreboard namespace. In order to read these items we need to read the xml stream from the item, there is no way to get the elements (why not ? This would be a great feature on the next release of the Feeds API). We load the xml in a document and select the nodes through an XPath expression, do not forget to add a NamespaceManager because we are using a different namespace than the default namespace. Here's the code.
private void UpdateScoreBoard() {
ListBox1.Items.Clear();
IFeedsManager manager = new FeedsManagerClass();
IFeedFolder rootFolder = (IFeedFolder)manager.RootFolder;
IFeed feed = (IFeed)rootFolder.GetFeed("ScoreBoard Project1");
foreach (IFeedItem item in (IFeedsEnum)feed.Items) {
XmlDocument document = new XmlDocument();
document.LoadXml(item.Xml(FEEDS_XML_INCLUDE_FLAGS.FXIF_NONE));
XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable);
namespaceManager.AddNamespace("scoreboard", "http://www.widec.be/blog/rss/samples/scoreboard");
XmlNode scoreNode = document.SelectSingleNode("item/scoreboard:score", namespaceManager);
XmlNode agentNode = document.SelectSingleNode("item/scoreboard:agent", namespaceManager);
if (scoreNode != null && agentNode != null) {
ListBox1.Items.Add(agentNode.InnerText + " - " + scoreNode.InnerText);
}
}
}
Since we want the listbox to be updated once the Rss Platform Engine refreshes the feed we need to bind events on the feed. We do this like I previously showed in my post "Microsoft Feeds API, event handlers", if you want an example in VB.Net there is one on the RSS team blog called Events in VB.NET. Again I have declared an IFeedEvents_Event interface on the form, this interface will hold the eventwatcher from the feed. How this is done is showed in the following code.
private void BindFeedEvents() {
IFeedsManager manager = new FeedsManagerClass();
IFeedFolder rootFolder = (IFeedFolder)manager.RootFolder;
IFeed feed = (IFeed)rootFolder.GetFeed("ScoreBoard Project1");
FeedEvents = (IFeedEvents_Event)feed.GetWatcher(FEEDS_EVENTS_SCOPE.FES_SELF_ONLY,
FEEDS_EVENTS_MASK.FEM_FEEDEVENTS);
FeedEvents.FeedDownloadCompleted +=
new IFeedEvents_FeedDownloadCompletedEventHandler(Feed_FeedDownloadComplete);
}
All we need to do now is declare an event handler to execute the code we want and we want the scoreboard to be updated. Here's the handler.
private void Feed_FeedDownloadComplete(string path, FEEDS_DOWNLOAD_ERROR error) {
UpdateScoreBoard();
}
After the InitializeComponent call in the constructor of the form you place a call to BindFeedEvents method and when you start the form you are instantly connected with updates on the feed. The complete source of the application is availlable here.
When coding this sample I had the following remarks.
- Why is the Feeds API not managed code ? This would be much easier to code and hey we live in 2006, why create solutions in COM ?
- Why is there no way to get custom elements out of the items, in a managed assembly this would be as easy as 1 + 1 = 2, as I showed in this sample.
- The possibilities of the feeds API go way beyond reading posts of blogs. It's a whole new way of thinking and developing, a big thanks to the RSS team, great work !!!
Like promised the next episode of the Really Simple Sample. In this episode I will present the next step of importing and exporting feed lists through an xml file. The format can now handle recursive folders and feeds like it is supposed to be. I have coded the FeedImportExport class, an instance of this class can import and export feeds and folders from/to an xml file, it makes use of the data classes FeedData, FeedFolderData and FeedImports.
using System;
using System.Collections.Generic;
using System.Text;
namespace Widec.Feeds{
public class FeedData {
[System.Xml.Serialization.XmlAttribute("name")]
public string Name;
[System.Xml.Serialization.XmlAttribute("url")]
public string Url;
}
public class FeedFolderData {
[System.Xml.Serialization.XmlAttribute("name")]
public string Name;
[System.Xml.Serialization.XmlElement("feed")]
public List Feeds;
[System.Xml.Serialization.XmlElement("folder")]
public List Folders;
}
[System.Xml.Serialization.XmlRoot("mfi")]
public class FeedImports {
[System.Xml.Serialization.XmlElement("folder")]
public List Folders;
[System.Xml.Serialization.XmlElement("feed")]
public List Feeds;
public static FeedImports Deserialize(string filename) {
using (System.IO.FileStream stream = new System.IO.FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) {
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(FeedImports));
return (FeedImports)serializer.Deserialize(stream);
}
}
public void Serialize(string filename) {
using (System.IO.FileStream stream = new System.IO.FileStream(filename,
System.IO.FileMode.Create, System.IO.FileAccess.ReadWrite)) {
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(FeedImports));
serializer.Serialize(stream,this);
}
}
}
}
The FeedImportExport class is a basic recursive import and export reader/writer. You use the Import and Export method to do your work, these methods call the recursive ImportFeed/ExportFeed and ImportFeedFolder/ExportFeedFolder methods. The Export method has one additional feature, it splits the pathname of the FeedFolder and generates the needed folder elements in the xml.
using System;
using System.Collections.Generic;
using System.Text;
using Feeds;
namespace Widec.Feeds {
public class FeedImportExport{
public void Import(FeedImports import) {
IFeedsManager manager = new FeedsManagerClass();
IFeedFolder rootFolder = (IFeedFolder)manager.RootFolder;
if (import.Folders != null) {
foreach (FeedFolderData folderData in import.Folders) {
ImportFeedFolder(folderData, rootFolder);
}
}
if (import.Feeds != null) {
foreach (FeedData feedData in import.Feeds) {
ImportFeed(feedData, rootFolder);
}
}
}
public FeedImports Export(string folderPath) {
FeedImports import = new FeedImports();
import.Folders = new List();
IFeedsManager manager = new FeedsManagerClass();
IFeedFolder exportFolder = (IFeedFolder)manager.GetFolder(folderPath);
string[] Folders = folderPath.Split('\\');
FeedFolderData data = null;
foreach (string folderName in Folders) {
FeedFolderData folderData = new FeedFolderData();
folderData.Name = folderName;
if (data == null){
import.Folders.Add(folderData);
}else{
data.Folders = new List();
data.Folders.Add(folderData);
}
data = folderData;
}
foreach (IFeed feed in (IFeedsEnum)exportFolder.Feeds) {
ExportFeed(feed, data);
}
foreach (IFeedFolder folder in (IFeedsEnum)exportFolder.Subfolders) {
ExportFeedFolder(folder, data);
}
return import;
}
private void ExportFeed(IFeed feed, FeedFolderData folderData) {
if (folderData.Feeds == null) {
folderData.Feeds = new List();
}
FeedData data = new FeedData();
data.Name = feed.name;
data.Url = feed.url;
folderData.Feeds.Add(data);
}
private void ExportFeedFolder(IFeedFolder folder, FeedFolderData folderData) {
if (folderData.Folders == null) {
folderData.Folders = new List();
}
FeedFolderData current = new FeedFolderData();
current.Name = folder.name;
folderData.Folders.Add(current);
foreach (IFeedFolder subFolder in (IFeedsEnum)folder.Subfolders) {
ExportFeedFolder(subFolder, current);
}
foreach (IFeed feed in (IFeedsEnum)folder.Feeds) {
ExportFeed(feed, current);
}
}
private void ImportFeed(FeedData data, IFeedFolder folder){
if (!folder.ExistsFeed(data.Name)) {
folder.CreateFeed(data.Name, data.Url);
}
}
private void ImportFeedFolder(FeedFolderData data, IFeedFolder folder){
IFeedFolder importFolder;
if (folder.ExistsSubfolder(data.Name)) {
importFolder = (IFeedFolder)folder.GetSubfolder(data.Name);
} else {
importFolder = (IFeedFolder)folder.CreateSubfolder(data.Name);
}
if (data.Feeds != null) {
foreach (FeedData feedData in data.Feeds) {
ImportFeed(feedData, importFolder);
}
}
if (data.Folders != null) {
foreach (FeedFolderData folderData in data.Folders) {
ImportFeedFolder(folderData, importFolder);
}
}
}
}
}
Really simple, but a very handy class. Next time I will create a form to view, import and export the Common Feed List and show how to use the FeedEnclosure.AsyncDownload and FeedEnclosure.CancelAsyncDownload, so stay tuned. At that time I will include the complete sample code.