Here is a step-by-step procedure indicating what I did in order to get this all to compile. The issue is that I can't use a C# 3.0-enabled compiler against the WPF application directly, because WPF won't compile (at least it wouldn't for me). So, the trick was to create a LINQ class library and reference it from a WPF application. Even though the LINQ class library uses C# 3.0 to compile - it still generates IL that is bound to version
2.0 of the .NET Framework - making it compatible with everything else that runs on
2.0, including WPF/WCF and WWF.
- Create a new WinFX Windows Application
- Click "File", then "Add" then "New Project" (make sure you don't just do File/New)
- Select the "LINQ Console" (C#) template. I had trouble getting this to show up , so I copied the "LINQ Console.zip" file to about 10 different spots in my Templates directory until it showed up ;) Three cheers for brute force!
- At this point you should have a WPF Windows app and a C# 3.0/LINQ app. Here's the rub: you will get ridiculous compile errors if your WPF app doesn't reference the LINQ assemblies. So, add the following assemblies to your WPF app's References:
- System.Data.DLinq
- System.Data.Extensions
- System.Query
- System.Xml.XLinq
- The library that I use to return a list of data to WPF actually returns data of type ObservableCollection, which has hooks in it to notify the WPF GUI when things change. In order for your LINQ class library to be able to see ObservableCollection, you need to add a reference to:
- WindowsBase
Now that your solution is set up, I can get to showing you the code. First, let's create the the class library. This library is going to consume a list of RSS feeds and return an aggregated list of all items, sorted descending. This essentially uses a couple of quick lines of XLinq to grab all the news you're interested in and cram it into one list.
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Data.DLinq;
using System.Xml.XLinq;
namespace RssLibrary
{
public static class LanguageExtender
{
public static string SafeValue(this XElement input)
{
return (input == null) ? string.Empty : (string)input.Value;
}
public static DateTime SafeDateValue(this XElement input)
{
return (input == null) ? DateTime.MinValue : DateTime.Parse(input.Value);
}
}
public static class RssConsumer
{
static Func<XElement, XElement, XElement> selectDate = (date1, date2) => ( date1 == null) ? date2 : date1 ;
private static string[] feeds = {
"http://feeds.feedburner.com/dotnetaddict"
};
public static List<RssArticle> GetArticles(DateTime origin)
{
XName xn = XName.Get("{http://purl.org/dc/elements/1.1/}creator");
XName xn2 = XName.Get("{http://purl.org/dc/elements/1.1/}date");
var feedQuery =
from item in GetFeedItems()
select new RssArticle {
Title = item.Element("title").SafeValue(),
Description = item.Element("description").SafeValue(),
Link = item.Element("link").SafeValue(),
PublicationDate = selectDate(item.Element(xn2), item.Element("pubDate")).SafeDateValue(),
Author = item.Element(xn).SafeValue(),
BlogName = item.Parent.Element("title").SafeValue(),
BlogAddress = item.Parent.Element("link").SafeValue()
};
feedQuery =
from item in feedQuery
where item.PublicationDate >= origin
orderby item.PublicationDate descending
select item;
return feedQuery.ToList();
}
public static IEnumerable<XElement> GetFeedItems()
{
foreach (var str in feeds)
{
var feed = XDocument.Load(str);
var items = feed.Root.Element("channel").Elements("item");
foreach (var item in items)
yield return item;
}
}
}
}
There are a couple of really cool things going on here. The method
GetFeedItems is using the new 2.0
yield return statement to easily create results of type
IEnumerable. There are two language extension methods in this file (you can read more about language extensions
here) that provide some logic on handling nulls. And finally, there's an XLinq query that tears through the list of
XElement objects returned by the
GetFeedItems method, sorts them by date, and grabs all the pertinent information. There is only one RSS feed in the array for this demo, but you can easily add as many as you like. I've run it with about 50 RSS feeds in it without a significant slowdown. Any more than that and you'll want to look into upgrading this code to asynchronous fetching :)
Next is the fun part: The
XAML. Take a look at the XAML for my main window:
<Window x:Class="WPFApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lib="clr-namespace:RssLibrary;assembly=RssLibrary"
Title="RSS Aggregation Viewer (WPF)" Height="410" Width="680"
Loaded="Window_Loaded"
>
<Window.Resources>
<ObjectDataProvider ObjectType="{x:Type lib:RssArticleList}" x:Key="articleProvider" />
<DataTemplate x:Key="articleTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75"/>
<ColumnDefinition Width="125" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="250" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=Author}" Grid.Column="0" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Path=PublicationDate}" Grid.Column="1" HorizontalAlignment="Center" />
<TextBlock Text="{Binding Path=Title}" Grid.Column="2" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Path=BlogName}" Grid.Column="3" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ListBox Name="articleList" Height="400" Width="650" ItemsSource="{Binding Source={StaticResource articleProvider}}"
ItemTemplate="{DynamicResource articleTemplate}">
<ListBox.BitmapEffect>
<DropShadowBitmapEffect Color="Gray" ShadowDepth="3"/>
</ListBox.BitmapEffect>
</ListBox>
</StackPanel>
</Window>
The
ObjectDataSource resource is used to create a link between the GUI and an instance of an object. The instance, in this case, is created automatically by WPF. You can either load the data in the constructor (as I've done here, though I don't recommend it for production apps), or you can specify the name of a method to be called on the instance to fetch the data. You can also programmatically obtain the instance and then manually associate it with the object data source, as I did with some of my earlier WPF examples based on the Feb CTP.
This little bit of code:
xmlns:lib="clr-namespace:RssLibrary;assembly=RssLibrary" is the magic gem that tells WPF that the object type to which I am binding is in the
RssLibrary namespace, which can be found in the
RssLibrary assembly. The only thing left to do is to create the
RssArticle class, which is just a collection of properties, and the
RssArticleList class, shown below:
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Text;
namespace RssLibrary
{
public class RssArticleList : ObservableCollection<RssArticle>
{
public RssArticleList()
{
List<RssArticle> theList = RssConsumer.GetArticles(DateTime.MinValue);
foreach (RssArticle article in theList)
{
this.Add(article);
}
}
}
}This is the object to which the GUI is bound. When it is first instantiated, it fetches all the feed items using the
RssConsumer class (which in turn uses
XLinq!!) and self-populates its own
ObservableCollection instance. This gives WPF the data it needs to bind to the interface and generate the output shown below:

I shouldn't need to tell you just how unbelievable the impact of this is. Couple the extremely powerful GUI/rendering capabilities of WPF with the easy, powerful, re-usable, scalable binding syntax, and add to that the ability to retrieve data from
anywhere using DLinq, LINQ, or XLinq - and you've got one unbelievable combination that
I think is the future of data-driven development on the Windows Vista platform - and you can quote me on that.
tags: vista data winfx xlinq binding csharp wpf linq dlinq
links: digg this del.icio.us technorati reddit