Creating a RssDataProvider for LINQ to Umbraco
Sorry to all the people who were kind enough to come to my LINQ to Umbraco session at CodeGarden 09, I said I would do this post soon after the session. Sadly I started enjoying Copenhagen too much without the need to be sitting at my laptop and now it's a week later, I'm home and it's time I come good on my promise.
The LINQ to Umbraco DataProvider model
Something that I have implemented with LINQ to Umbraco, and something which will be taking a stronger focus in Umbraco going forward, is a Provider model for the Umbraco data. What this means with LINQ to Umbraco? Well the classes generated for LINQ to Umbraco act as proxies to a data model, they don't expect the data to come from anyway in particular.
This has a really neat advantage of the fact that you can write your own DataProvider which exposes the data from how ever you want. LINQ to Umbraco will ship as part of 4.1 with a single DataProvider, the NodeDataProvider. This enables the use of LINQ to Umbraco against the XML cache, which was the inital design of it.
Anatomy from a DataProvider
The DataProvider itself is an abstract class which has a number of methods which are implemented do different operations, the primary method you need to be implementing is the LoadTree
There are other methods which have different uses, I wont be covering them in this post, but they will be going up on the new Umbraco wiki (which the LINQ to Umbraco section is starting to come up).
The LoadTree<TDocType> method needs to then return an instance of a Tree<TDocType>, which is another abstract class that needs to be implemented to handle the data mapping for your data provider.
Creating an RssDataProvider
While we were hacking at the Umbraco Retreat prior to CodeGarden 09 I decided to try a proof-of-concept about how you could use the generated classes in a proxy manner. I may have written LINQ to Umbraco for this purpose, but it wasn't something that I had actually tried to do. So I decided to create a basic little DataProvider which would read an RSS feed and turn the returned data from there into LINQ to Umbraco objects which could then be used in the Umbraco content tree.
The first step is you need to generate the LINQ to Umbraco classes, with Umbraco 4.1 you will be able to do this directly from the Settings -> Document Types node. I created a basic little Document Type named RSS Item and then generated the class for it.
Next came the task of implementing my custom UmbracoDataProvider, I created my class:
public class RssDataProvider : UmbracoDataProvider { }
And then set about implementing a constructor which takes the RSS feed URL (in this demo I used the Yahoo! Pipes which are on the homepage of our.umbraco) and then implemented the LoadTree
public override Tree LoadTree() { //supporting loading a full Tree //throw an exception if the type of the tree is an unsupported one if (typeof(TDocType) != typeof(RssItem)) { throw new NotSupportedException(typeof(TDocType).Name); }
//create a request to the URL supplied
WebRequest request = WebRequest.Create(this._feedUrl);
//do a GET and string buffer the response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();
//make a LINQ to XML representation of the RSS
XDocument xdoc = XDocument.Parse(responseFromServer);
//select the posts
var items = xdoc.Descendants("item");
//make an RssTree from the items returned by the feed
return new RssTree(items, this);
}
So now I have a load method which reads my RSS feed (and I've restricted it to only support my RssItem Document Type), now it's time to create the RssTree
Tree<TDocType>
This class is really just a wrapper for the IEnumerable
The following code is a bit of a hack (for the return type anyway) but that is because I wanted to show it being done without the use of reflection. If you want to see how to achieve it with a complete generic type check out the source for the NodeTree which is on Codeplex.
Anyway here's how the GetEnumerator() method looks:
public override IEnumerator GetEnumerator()
{
//this is a bit hacky as i only support 1 doc type
//normally the load can be done via reflection (which is how the NodeTree works)
foreach (var item in _items)
{
var rssItem = new TDocType() as RssItem;
rssItem.Name = (string)item.Element("title");
rssItem.Link = (string)item.Element("link");
rssItem.Description = (string)item.Element("description");
rssItem.PublishDate = (DateTime)item.Element("pubDate");
rssItem.Content = (string)item.Element("content");
rssItem.CreateDate = (DateTime)item.Element("pubDate");
//Because RssItem may not be the type of TDocType (although in this example we'll assume it always is)
//we have to downcast to DocTypeBase before casting to the generic.
yield return (TDocType)(DocTypeBase)rssItem;
}
}
So what's going on, well first off we're itterating through the collection of XML items returned from the initial load (_items) and then creating a new instance of the RssItem class and assigning the properties from the XML. You can see the comment mentioning the hack, having to do some crazy casting, that is because I'm not really doing the Generics properly.
I've also implemented it via yield return, not building the entire collection into say a List
Any that is as simple as it gets to write your own custom DataProvider for LINQ to Umbraco! Sure I've skipped a few sections (such as how to do child associations, but in this demo it's not really viable) but hopefully this should give you a heads up on how to do it. And how does it work? Well just like this:
using (var ctx = new RssDataContext(new RssDataProvider("http://pipes.yahoo.com/pipes/pipe.run?_id=8llM7pvk3RGFfPy4pgt1Yg&_render=rss")))
{
var feedItems = ctx.RssItems.Take(8);
Assert.IsNotNull(feedItems);
Assert.IsTrue(feedItems.GetEnumerator() != null);
}
That's right, the above code is from a unit test, remember LINQ to Umbraco is capable of running outside of a web context so it is very easy to unit test!
Making this into an Umbraco Content Tree
Now here is where the fun part comes in, you can easily turn the above data provider into a custom Umbraco tree. This means you can either make it into your own custom Umbraco module (/ application, what ever you call it!), or append it to the standard Content Tree! Isn't THAT a funky idea hey!
I'm not going to get too in-depth into this, Shannon Deminik has done some good documentation about that (again, see the wiki). So rather than going over the code I'm going to show it off in a short screencast and you can look into the provided source package with this post.
The screenscast is available here and the source code is here.
Comments
Vincent Baaij
Hi,
The scenario shown in your screencast is something I could realy use in a current project.
I've downloaded and looked at the source you provided, but am stumped on how to use that and get the result shown in the screencast. The link to the documentation didn't get me any further (that could also tell somthing about me :)).
Can you help?
Thanks in advance.
Vincent Baaij
Aaron, thanks for the link. I've taken a good look at the code and see the advance over the RssDataProvider. What I don't grasp yet is how I can achieve the appending of items to the standard Content Tree.
Am I on the right track when I think I need to add RssDataProviderTree.cs and RssDataProviderContentTree.cs from the code in this article to the code in your link?
Aaron Powell
Yes, and modify the database to use your proper content tree.
Although I don't know why you'd bother using LINQ to Umbraco for it, the code above is for a PoC and it's not really useful in a real-world scenario.
Vincent Baaij
Well, I'am still learning Umbraco and since you know a lot more about this stuff than me...
What I'am trying to do is to make an 'import' action that reeds a RSS Feed at certain specified times (through schedduling of some kind). Each item in the feed should be added to the tree as an item of a specific DocumentType. The type could be something like the RssItem in this sample (but doesn't have to be!)
Do you think an approach with LINQ to Umbraco is a viable option, or am I going about it completely the wrong way?
Aaron Powell
If you've got an implementation question you're better asking at the our.umbraco website. Chances are someone else will also see it and they will have done a similar thing as you are trying to achieve :)
Aaron Powell
@Vincent Baaij - This code is very out of date, and I don't think it'd even compile against Umbraco 4.5. You'd be better looking at my LINQ to Umbraco Extensions which is actually rather complete, I just haven't had time to blog about how it works.