Contents
.NET 3.0 has now been released, so we should all know it by now shouldn't we. Jeez, it doesn't seem like that long ago that .NET 2.0 came along. Well for those that dont realize, .NET 3.0 actually contains quite a lot of new stuff, such as:
- Windows Workflow Foundation (WWF) : Managing object lifecycles / persistent object storage
- Windows Communication Foundation (WCF) : The new communication layer
- Windows Presentation Foundation (WPF) : The new presenstation layer (XAML)
- Windows Cardspace : Which provides a standards-based solution for working with and managing diverse digital identities
So as you can see there is a lot to be learned right there. I'm in the process of learning WPF/WCF but I am also interested in a little gem called LINQ, that I believe will be part of .NET 3.5 and Visual Studio "Orcas" (as its known now). LINQ will add new features to both C# and VB.NET. LINQ has three flavours:
- LINQ : Language Integrated Query for in memory objects (renamed in March 2007 Orcas CTP to LINQ to objects)
- DINQ : Language Integrated Query for databases (renamed in March 2007 Orcas CTP to LINQ to SQL)
- XLINQ : Language Integrated Query for XML (renamed in March 2007 Orcas CTP to LINQ to XML)
LINQ is pretty cool, and I have been looking into it as of late, so I thought I would write an article about what I have learned in the LINQ/DLINQ/XLINQ areas, in the hopes that it may just help some of you good folk. This article will be focused on XLINQ, and is the third in a series of three articles.
The article series content is as follows:
- Part1 : will be all about standard LINQ, which is used to query in memory data objects such as List, arrays etc etc
- Part2 : will be about using DLINQ, which is LINQ for database data
- Part3 (this article) : will be about using XLINQ, which is LINQ for XML data
Before I start bombarding people with even more information than they can possibly handle, let me just talk briefly about the files attached to this article.
This article contains two seperate zip files. The zip files are the two demo applications.
DEMO application 1
Is based on using Visual Studio 2005 with .NET 2.0 Framework, and the May 2006 LINQ CTP which is available here
See that the XML is syntax highlighted? This is all thanks to the free and fantastic Fireball code highlighter, available right here at Code Project.
The main window allows the user to try out various XLINQ activites such as :
- Create a new XML tree section
- Create a new XML document
- Open an existing XML document
- Find root node attributes
- Query the XML using standard LINQ query operators as described in Part1 of this series
- Updating XML sections
- Deleting XML sections
The 2nd form within this 1st demo application (available from "What About A Web Query" menu), uses XLINQ to get an RSS feed from Flickr, using a Flickr API string. You should not need to download anything extra for this to work, as it is all in the code. The Flickr RSS feed is then queried (this is what LINQ is all about, Language Intergrated Query Language, remember) using XLINQ, and the results are put into a new structure, using standard LINQ projection, and then the results are then used to populate a FlowLayoutPanel
with PictureBox
controls that represent the gathered images. The user is also free to change the type of Flickr search being performed, and may page through the results obtained.
DEMO application 2
Is based on using Visual Studio 2005 with The Visual Studio Designer for WPF installed, or using Expression BLEND and Visual Studio 2005 combination, or WordPad if you prefer to write stuff in that.
Obviously as it's WPF you will also need the .NET 3.0 framework, and the May 2006 LINQ CTP which is available here. The idea being that those folk that want to see how WPF/XAML will work with LINQ can have a look at this project. Don't worry to much if you dont get this code, as this application will be the subject of my next article. It's really being included for interest at this point, to show that LINQ and WPF can work quite nicely together.
This is basically doing the same thing as the 2nd form of DEMO application 1, it just looks a whole lot nicer, and showcases some cool WPF stuff like:
- Animations
- Data binding
- Templating
- Styles
- Resources
- XLINQ usage, of course
This application is not really that orientated to XLINQ, but it is something that I just wanted to try, so I thought, as I've done it, why not include it here. As I say if you dont get DEMO application 2, don't worry, I will be going through it in another article. It's here for interest's sake.
The full article which explains this WPF application can be found here
- To run the code supplied with this article you will need to install the May 2006 LINQ CTP which is available here, there is a new March 2007 CTP available, but it's about 4GB for the full install (as its not just LINQ but the entire next generation of Visual Studio codenamed "Orcas") and quite fiddly, and probably going to change anyway, so the May 2006 LINQ CTP will be OK for the purpose of what this article is trying to demonstrate.
- DEMO application 1, makes use of the excellent Fireball code highlighter, available right here at Code Project.
- DEMO application 2, makes use of the .NET 3.0 Framework which is available for download here
So what is this XLINQ stuff? Well, it's one flavour of LINQ (Language Integrated Query) that will be part of .NET 3.5, and will certainly be part of the next verison of Visual Studio (currently called Orcas).
As a recap, recall that LINQ dealt with in memory objects such as arrays and List
and Dictionary
objects, whilst DLINQ or LINQ over SQL dealt with entities and database interaction. So any guesses what XLINQ is all about? Well, it's actually XML over LINQ.
So what else can we say about this XLINQ stuff? We shall ask Microsoft what their marketing blurb is, as far as XLINQ is concerned. I asked them and they said this:
"XLinq was developed with Language Integrated Query over XML in mind from the beginning. It takes advantage of the Standard Query Operators and adds query extensions specific to XML. From an XML perspective XLinq provides the query and transformation power of XQuery and XPath integrated into .NET Framework languages that implement the LINQ pattern (e.g., C#, VB, etc.). This provides a consistent query experience across LINQ enabled APIs and allows you to combine XML queries and transforms with queries from other data sources. We will go in more depth on XLinq's query capability in section 3, "Querying XML with XLinq".
Just as significant as the Language Integrated Query capabilities of XLinq is the fact that XLinq represents a new, modernized in-memory XML Programming API. XLinq was designed to be a cleaner, modernized API, as well as fast and lightweight. XLinq uses modern language features (e.g., generics and nullable types) and diverges from the DOM programming model with a variety of innovations to simplify programming against XML. Even without Language Integrated Query capabilities XLinq represents a significant stride forward for XML programming. The next section of this document, "Programming XML", provides more detail on the in-memory XML Programming API aspect of XLinq."
(Taken from XLINQ overview.doc, available at the LINQ Project [1] web site)
I think that it is quite a good description of what XLINQ is and promises to be. Of course, it should be a good sales blurb, as XLINQ is a Microsoft invention. But what does this all mean to the average developer?
This is something that this article will try and demonstrate, by the use of textual comments, and code snippets and real life working examples.
Unless stated otherwise the example code shown within this article will be for discussion purposes only. But don't worry, I've gone out of my way to actually trawl the XLINQ documentation and make a nice little demo application that contains real-life working examples, of how to use XLINQ to do some of the most commonly occurring XML related tasks. So this working code is included within the DEMO application 1, and where I am specifically using the code from the DEMO application 1, I will show the following image to let you know that you can look in the code for a working example.
When you see this image, this means that I have created a worked example for you in DEMO application 1.
Obviously I have not got an example of every single thing that can be done with XLINQ, as I do have a life (not much of one, but one nonetheless). So I'll have to leave further investigation as an excercise for the reader.
The way that this article is constructed is by looking at the following items, that I would consider to be very important issues that every developer that works with xml should know.
- The XLINQ class structure
- Creating new XML
- Saving XML
- Loading XML
- Traversing XML
- Working with the root node
- Appending new elements
- Updating existing elements
- Deleting existing elements
- Querying XML
- Using XLINQ to access 3rd party XML data
I'm hoping that by going through this set of items, that by the end, the reader will have at least a basic appreciation of what can be done with XLINQ and how it could be used inplace of using the DOM and XPath.
So shall we continue?
The overall XLINQ class heirachy is as shown below.
- Although XElement is low in the class hierarchy, it is the fundamental class in XLinq. XML trees are generally made up of a tree of XElements. XAttributes are name/value pairs associated with an XElement. XDocuments are created only if necessary, such as to hold a DTD or top level XML processing instruction (XProcessingInstruction). All other XNodes can only be leaf nodes under an XElement, or possibly an XDocument (if they exist at the root level).
- XAttribute and XNode are peers and not derived from a common base class (other than object). This reflects the fact that XML attributes are really name value pairs associated with an XML element not nodes in the XML tree. Contrast this with W3C DOM.
- XText is exposed in this version of XLinq, but as discussed above, it is best to think of it as a semi-hidden implementation detail except when exposing text nodes is necessary. As a user, you can get back the value of the text within an element or attribute as a string or other simple value.
- The only XNode that can have children is an XContainer, meaning either an XDocument or XElement. An XDocument can contain an XElement (the root element), an XDeclaration, an XDocumentType, or an XProcessingInstruction. An XElement can contain another XElement, an XComment, an XProcessingInstruction, and text (which can be passed in a variety of formats, but will be represented in the XML tree as text).
Taken from XLINQ overview.doc, available at the LINQ Project [1] web site.
In order to undestand how XLINQ is different from existing XML document creation pratices, let's consider the following section of traditional DOM (Document Object Model) code, that creates a small XML document.
XmlDocument doc = new XmlDocument();
XmlElement name = doc.CreateElement("name");
name.InnerText = "Patrick Hines";
XmlElement phone1 = doc.CreateElement("phone");
phone1.SetAttribute("type", "home");
phone1.InnerText = "206-555-0144";
XmlElement phone2 = doc.CreateElement("phone");
phone2.SetAttribute("type", "work");
phone2.InnerText = "425-555-0145";
XmlElement street1 = doc.CreateElement("street1");
street1.InnerText = "123 Main St";
XmlElement city = doc.CreateElement("city");
city.InnerText = "Mercer Island";
XmlElement state = doc.CreateElement("state");
state.InnerText = "WA";
XmlElement postal = doc.CreateElement("postal");
postal.InnerText = "68042";
XmlElement address = doc.CreateElement("address");
address.AppendChild(street1);
address.AppendChild(city);
address.AppendChild(state);
address.AppendChild(postal);
XmlElement contact = doc.CreateElement("contact");
contact.AppendChild(name);
contact.AppendChild(phone1);
contact.AppendChild(phone2);
contact.AppendChild(address);
XmlElement contacts = doc.CreateElement("contacts");
contacts.AppendChild(contact);
doc.AppendChild(contacts);
Whilst this is fairly easy to do, what is not very clear is the structure that is emmitted to the actual XML document. It is possible to work it out, for this trivial example, but if this were a large XML document, it would not be so clear. Shall we have a look at how XLINQ goes about doing the same job?
XElement contacts =
new XElement("contacts",
new XElement("contact",
new XElement("name", "Patrick Hines"),
new XElement("phone", "206-555-0144",
new XAttribute("type", "home")),
new XElement("phone", "425-555-0145",
new XAttribute("type", "work")),
new XElement("address",
new XElement("street1", "123 Main St"),
new XElement("city", "Mercer Island"),
new XElement("state", "WA"),
new XElement("postal", "68042")
)
)
);
The first thing to say is that this is a lot less code, and the tree structure is almost self-evident from this listing - and it's almost as easy to read as an XML document. The other thing to note, is that in the actual syntax itself, nowhere do we see the methods createElement()
, or AppendChild()
. Instead we see new XElement
, which sort of makes more sense, at least in how it actually reads. Personally, I think someone who knows XML, but has not used DOM before would probably understand this new syntax a bit better, as it seems to be a closer match in terms of the actual terminology used. An XML man knows of a new element as a new element, not as AppendChild()
. I guess in the end, this is really down to preference.
DEMO application 1, contains three menus to further demonstrate this concept, look at :
- Programming LINQ\Create Elements menu
- Programming LINQ\Create Entire Document menu
- Programming LINQ\Create Elements With Namespaces menu
Both XDocument
and XElement
expose an overloaded Save() method, the options are as follows:
XDocument.Save(string fileName)
This option simply saves the XElements content to the location specfied by the fileName parameter.
XDocument.Save(System.Xml.XmlWriter writer)
This option simply saves the XElements content to an XmlWriter
XDocument.Save(TextWriter textWriter)
This option simply saves the XElements content to an TextWriter
XDocument.Save(string fileName, bool preserveWhitespace)
This option simply saves the XElements content to the location specfied by the fileName parameter, and preserves any white space.
The same methods exist for XElement
Both XDocument
and XElement
exposes an overloaded Load() method, the options are as follows:
XDocument.Load(string uri)
This option simply loads the XML element from the location specified by the uri into a new XDocument
XDocument.Save(System.Xml.XmlReader reader)
This option simply reads the contents of an XmlReader
into a new XDocument
XDocument.Save(TextReader textReader)
This option simply reads the contents of a TextReader
into a new XDocument
XDocument.Save(uri fileName, bool preserveWhitespace)
This option simply reads the contents of the file specfied by the fileName parameter, and preserves any white space, into a new XDocument
There is also another possibility to load XML. We can parse a string by calling the XDocument.Parse
method, so lets see an example of that shall we?
XElement contacts = XElement.Parse(
@"<contacts>
<contact>
<name>Patrick Hines</name>
<phone type=""home"">206-555-0144</phone>
<phone type=""work"">425-555-0145</phone>
<address>
<street1>123 Main St</street1>
<city>Mercer Island</city>
<state>WA</state>
<postal>68042</postal>
</address>
<netWorth>10</netWorth>
</contact>
</contacts>");
Once we have loaded, or parsed a string, the full power of XLINQ may be used. We can traverse through nodes, inspect attributes, create new content, delete existing content.
The same methods exist for XElement
DEMO application 1, contains 2 areas to further demonstrate this concept, look at :
- Programming LINQ\Opem Existing File menu (this shows the
Load()
method) - Look at the
createQuerySource()
method (this shows the Parse()
method)
XLINQ provides methods for getting the children of an XElement
. To get all of the children of an XElement
(or XDocument
), you can use the Nodes()
method.
For example if we had the following XML
And ran the following traversal
foreach (XElement c in contacts.Nodes())
{
....
}
We would actually end up with the orginal XML.
So how about getting to sub-elements? Well, it's very simliar syntax infact. Let's see an example shall we?
foreach (XElement c in contacts.Elements("contact").Elements("address"))
{
....
}
If we are using an XDocument
, this returns IEnumerable<object>
because you could have text mixed with other XLINQ types, such as XDeclaration
, XComment
and XProcessingInstruction
as well as XElement
Do how do we deal with this, our foreach (XElement in ....
is going to fail for non XElement
cases. What do we do about that? Well, quite simply put we do the following:
foreach (XElement c in contactsDoc.Nodes().OfType<XElement>())
{
....
}
DEMO application 1, contains three menus to further demonstrate this concept, look at :
- Traversing\Show All menu (this shows all Elements)
- Traversing\Show only Elements menu (this shows ONLY
XElement
types) - Traversing\Show Sub Elements menu (this shows the address sub elements of a Contact element)
Suppose that we have the following XML structure:
<contacts numContacts="1">
<contact>
<name>sacha barber</name>
<phone type="home">01273 45426</phone>
<phone type="work">01903 205557</phone>
<address>
<street1>palmeira square</street1>
<city>brighton</city>
<county>east sussex</county>
<postcode>BN3 2FA</postcode>
</address>
</contact>
</contacts>
And we wish to work with the root node attributes. We would normally (if using DOM) need to obtain a reference to the document node, then use that to obtain the attribute vales. XLINQ does things a little differently.
XElement root = contactsDoc.Root;
sb.Append(
"Examining root element [" + root.Name + "] for attributes\r\n\r\n");
if (root.HasAttributes)
{
foreach (XAttribute xa in root.Attributes())
{
sb.Append(
"Found Attibute [" + xa.Name.ToString() + "] Value=" + xa.Value);
}
}
txtResults.Document.Text = sb.ToString();
And that is enough to get the attributes of the root node. This assumes that the previous XML structure has been loaded into an XDocument
called contactsDoc. The result can be seen from this screenshot from DEMO application 1.
DEMO application 1, contains 1 menu to further demonstrate this concept, look at:
- Programming LINQ\Get Root Node Attribute Values menu (this shows how to get the root node attributes)
Appending new content is fairly trivial with XLINQ. It's all about getting a reference to a particular XDocument
or XElement
that you wish to add new content to. After that the work is fairly easy. Let's see an example shall we?
This example assumes that there is a pre-existing XDocument
called contactsDoc.
XElement root = contactsDoc.Root;
root.Add(new XElement("contact",
new XElement("name", "melissa george"),
new XElement("phone", "01273 999999",
new XAttribute("type", "office")),
new XElement("phone", "01903 888888",
new XAttribute("type", "work")),
new XElement("address",
new XElement("street1", "churchill square"),
new XElement("city", "brighton"),
new XElement("county", "east sussex"),
new XElement("postcode", "BN3 4RG")
)
)
);
This one bit of code created a new contact XElement
which is then appended to the root element of the existing XDocument
called contactsDoc. That's all you have to do, get the object you want to add content to, and add the content using the Add()
method
DEMO application 1, contains 1 menu to further demonstrate this concept, look at :
- Programming LINQ\Append New Contact menu (this shows how to add a new Contact to the root element of the current
XDocument
)
Updating existing content is something that anyone that works with XML is going to need to do at some stage. This could consist of updating an entire elements content, or could be simply just updating one sub element for an existing element. Because I'm nice, i'll show you both.
Let's assume we have the following source XML that we will update.
Update Entire Element
For the case where we want to update an entire element with new content, as before we simply get the object we want to apply the replacement content to, and replace it using the ReplaceContent()
method.
IEnumerable<XElement> singleContact = (from c in contactsDoc.Root.Elements(
"contact")
where ((string) c.Element(
"name")).Equals("sarah dudee")
select c);
foreach (XElement xe in singleContact)
{
xe.ReplaceContent(new XElement("name", "sam weasel"),
new XElement("phone", "01273 111111",
new XAttribute("type", "office")),
new XElement("phone", "01903 33333",
new XAttribute("type", "work")),
new XElement("address",
new XElement("street1", "the drive"),
new XElement("city", "brighton"),
new XElement("county", "east sussex"),
new XElement("postcode", "BN3 4RG")
)
);
}
This is enough to replace the entire Contact element where where the existing Contact element that had a name of "sarah dudee" is replaced
DEMO application 1, contains 1 menu to further demonstrate this concept, look at:
- Programming LINQ\Update Entire Contact Element menu (this shows how to update an entire Contact element of the current
XDocument
)
Update Sub Element Only
For the case where we ONLY want to update an portion of an existing element with new content, as before we simply get the object we want to apply the replacement content to, but this time we must manipulate the elements data directly.
IEnumerable<XElement> firstContact = ( from c in contactsDoc.Root.Elements(
"contact")
select c).Take(1);
foreach (XElement xe in firstContact)
{
xe.Element("address").SetElement("city", "MANCHESTER");
xe.Element("address").Element("postcode").ReplaceContent("MN1");
}
This is enough to replace the the sub element of the Contact element.
DEMO application 1, contains 1 menu to further demonstrate this concept, look at :
Programming LINQ\Update Only 1 Contact Detail menu (this shows how to update a sub-element of an exiting element of the current XDocument
)
Deleting existing content is also going to something that anyone that works with XML is going to need to do at some stage.
Let's assume we have the following source XML that we will delete from:
So as before we simply get the object we want to delete and then delete it using the Remove()
method.
IEnumerable<XElement> firstContact = ( from c in contactsDoc.Root.Elements(
"contact")
select c).Take(1);
foreach (XElement xe in firstContact)
{
xe.Element("address").Element("county").Remove();
}
This is enough to delete the entire Contact element where the existing 1st Contact element is deleted (remember we can use any of the standard LINQ query operators, so I'm simply using Take(1) which gives us the 1st Contact element)
DEMO application 1, contains 1 menu to further demonstrate this concept, look at :
- Programming LINQ\Delete A Contact menu (this shows how to delete an entire Contact element out of the current
XDocument
)
Recall in Part1 I introduced LINQ standard query operators, well guess what, XLINQ uses those very same standard query operators to allow programmers to query the XML tree in order to pull out certain parts of the tree for manipulation. Much the same as XPath allows.
The only limit here, is how good your standard query operator skills are. So let's see some examples shall we? I've got four little examples for you.
The following queries all work based on the same base XML, which is created within the DEMO application 1, within the createQuerySource()
method.
Get 1st Contact Query
XElement contactsElements = createQuerySource();
XElement res = new XElement("contacts",
(from c in contactsElements.Elements("contact")
select new XElement("contact",
c.Element("name"),
new XElement("phoneNumbers", c.Elements("phone"))
)).Take(1)
);
Get All Phone Numbers Query
XElement contactsElements = createQuerySource();
foreach (XElement phone in contactsElements.Elements("contact").Elements(
"phone"))
{
}
Get Specific Customer Query
XElement contactsElements = createQuerySource();
....
....
IEnumerable<XElement> cont = (from c in contactsElements.Elements("contact")
where (string) c.Element("name") == userName
select c);
....
....
Get ONLY Customers That Have Home Phone Numbers Query
XElement contactsElements = createQuerySource();
....
....
IEnumerable<XElement> res = (from c in contactsElements.Elements("contact")
where (string) c.Element(
"phone").Attribute("type") == "home"
select c);
....
....
DEMO application 1, contains 4 menus to further demonstrate this concept, look at :
- Queries\Get 1st Contact menu (Get the 1st Contact element)
- Queries\Get Phone Numbers menu (Get the phone numbers of all Contact elements)
- Queries\Get Specific Customer menu (Prompts the user for a Contact name and gets the Contact with that name, if it exists)
- Queries\Get Customers That Have Home phone numbers menu (Gets all Contacts that have home phone number attributes)
So how about we get XLINQ to grab some data out of an RSS Feed to show some images? After all, it should be able to cope with any XML data, even if it's not local. I decided to use Flickr as the data source, as I know that it has an API which may be used to obtain RSS Feeds with images URLs. Images are quite pretty, and are also suitable things to display in a Windows Form application. This concept is based on something that I saw in Omar Al Zabirs fantastic article which is available here
So shall we have a look at the code that does this? The class diagram is as follows:
As can be seen there is a form that has some Button
(btnGo/btnPrev/btnNext) on it and a ComboBox
(cmbSearchType) which are used to create the correct parametized RSS feed request from Flickr. The Flickr request is made by the RSSImageFeed
class, which returns a IEnumerable<PhotoInfo>
object which represents the photos found by the current RSS Feed request.
That's really all there is to it.
Now some code. To be honest it's very straightforward.
FlickrRSSGrabber
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;
namespace Linq_3
{
public partial class FlickrRSSGrabber : Form
{
private RSSImageFeed RSSImages = new RSSImageFeed();
public FlickrRSSGrabber()
{
InitializeComponent();
}
private void btnGo_Click(object sender, EventArgs e)
{
RSSImages.PageIndex = 0;
doFlickrSearch();
}
private void doFlickrSearch()
{
string searchType = cmbSearchType.SelectedItem.ToString();
string searchWord = searchType.Equals(
"By Search Word") ? this.txtSearchWord.Text : "";
if (searchType.Equals("By Search Word") && string.IsNullOrEmpty(
searchWord))
{
MessageBox.Show(
"You MUST enter a search word, to search by keyword");
}
else
{
getFlickrData(searchType, searchWord);
}
}
private void getFlickrData(string searchType, string searchWord)
{
IEnumerable<PhotoInfo> photos = (
IEnumerable<PhotoInfo>)RSSImages.LoadPictures(
searchType, searchWord);
if (photos.Count() > 0)
{
lblPageIngex.Visible = true;
int pIndex = RSSImages.PageIndex;
lblPageIngex.Text = "Page " + (++pIndex);
pnlFlowPhotos.Controls.Clear();
foreach (PhotoInfo pi in photos)
{
PictureBox pb = new PictureBox();
pb.ImageLocation = @pi.PhotoUrl(true);
pb.SizeMode = PictureBoxSizeMode.AutoSize;
toolTip1.SetToolTip(pb, pi.Title);
pnlFlowPhotos.Controls.Add(pb);
}
setNextPrevStates(RSSImages.IsPrevAvail,
RSSImages.IsNextAvail);
}
else
{
lblPageIngex.Visible = false;
setNextPrevStates(false, false);
}
}
private void setNextPrevStates(bool prevEnabled, bool nextEnabled)
{
btnPrev.Enabled = prevEnabled;
string prevTT = btnPrev.Enabled ? "Click to go back a page" :
"There are no more pages";
toolTip1.SetToolTip(btnPrev, prevTT);
btnNext.Enabled = nextEnabled;
string nextTT = btnNext.Enabled ? "Click to go forward a page" :
"There are no more pages";
toolTip1.SetToolTip(btnNext, nextTT);
}
private void cmbSearchType_SelectedValueChanged(object sender,
EventArgs e)
{
string searchType = cmbSearchType.SelectedItem.ToString();
txtSearchWord.Visible = searchType.Equals(
"By Search Word") ? true : false;
lblSearchWord.Visible = txtSearchWord.Visible;
}
private void btnNext_Click(object sender, EventArgs e)
{
RSSImages.PageIndex++;
doFlickrSearch();
}
private void btnPrev_Click(object sender, EventArgs e)
{
RSSImages.PageIndex--;
doFlickrSearch();
}
private void FlickrRSSGrabber_Shown(object sender, EventArgs e)
{
cmbSearchType.SelectedItem = "Most Recent";
getFlickrData("MOST_RECENT", "");
}
}
}
RSSImageFeed
To understand how this class uses XLINQ to obtain the Flickr data, one first needs to understand what the raw XML data is, that Flickr provides. It can be seen that there are three possible URLs listed below, for :
- MOST_RECENT
- INTERESTING
- ENTER_TAG
One of these three different URLs is triggered depending on the value that the user picks from the combobox (cmbSearchType) within the FlickrRSSGrabber
class shown above.
So let's examine a small subset of what Flickr gives us for one of these requests. Let's take the MOST_RECENT URL
http://www.flickr.com/services/rest/?method=flickr.photos.getRecent&api_key=c705bfbf75e8d40f584c8a946cf0834c
We end up with something like this. Try it yourself in FireFox, or your favourite browser.
<rsp stat="ok">
<photos page="1" pages="10" perpage="100" total="1000">
<photo id="493415381" owner="8201860@N03" secret="dba0cae590" server="225"
farm="1" title="setting up 6" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="493415375" owner="42802631@N00" secret="596a13de8e" server="226"
farm="1" title="DSC01713" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="493392504" owner="59616645@N00" secret="49459bb023" server="191"
farm="1" title="IMG_0841" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="493392488" owner="28532182@N00" secret="ebcdc8d2d8" server="220"
farm="1" title="img_1438" ispublic="1" isfriend="0" isfamily="0"/>
Looking at this structure, it's fairly easy to see how this small bit of XLINQ syntax (taken from the full code listing shown below) is working.
IEnumerable<PhotoInfo> photos = (from photo in xroot.Element(
"photos").Elements("photo")
select new PhotoInfo
{
Id = (string)photo.Attribute("id"),
Owner = (string)photo.Attribute("owner"),
Title = (string)photo.Attribute("title"),
Secret = (string)photo.Attribute("secret"),
Server = (string)photo.Attribute("server"),
Farm = (string)photo.Attribute("Farm"),
}).Skip(pageIndex * columns * rows).Take(columns * rows);
There is almost a direct mapping here, and it's very easy to read. Compare this with the iterative process that one would have to do if using the DOM, or compare it to its equivalent XPath. Me personally, I prefer XPath over the iterative DOM process any day of the week, but I prefer XLINQ process to XPath process. It's just so easy to read.
Anyway, full code listing for those that want the whole picture.
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;
namespace Linq_3
{
public class RSSImageFeed
{
private const string FLICKR_API_KEY =
"c705bfbf75e8d40f584c8a946cf0834c";
private const string MOST_RECENT =
<a href="http://www.flickr.com/services/rest/?method=flickr.photos.">http:
"getRecent&api_key=" + FLICKR_API_KEY;
private const string INTERESTING =
<a href="http://www.flickr.com/services/rest/?method=flickr.">http:
"interestingness.getList&api_key=" + FLICKR_API_KEY;
private const string ENTER_TAG = <a href="http://www.flickr.com/services">http:
"/rest/?method=flickr.photos.search&api_key=" +
FLICKR_API_KEY + "&tags=";
private string url = MOST_RECENT;
private int pageIndex = 0;
private int columns = 5;
private int rows = 2;
private bool prevAvail = false;
private bool nextAvail = false;
public RSSImageFeed()
{
}
public bool IsPrevAvail
{
get { return prevAvail; }
}
public bool IsNextAvail
{
get { return nextAvail; }
}
public int PageIndex
{
set { pageIndex = value; }
get { return pageIndex; }
}
public IEnumerable<PhotoInfo> LoadPictures(string searchType,
string searchWord)
{
switch (searchType)
{
case "Most Recent":
this.url = MOST_RECENT;
break;
case "Interesting":
this.url = INTERESTING;
break;
case "By Search Word":
this.url = ENTER_TAG + searchWord;
break;
default:
this.url = MOST_RECENT;
break;
}
try
{
var xraw = XElement.Load(url);
var xroot = XElement.Parse(xraw.Xml);
IEnumerable<PhotoInfo> photos = (
from photo in xroot.Element("photos").Elements("photo")
select new PhotoInfo
{
Id = (string)photo.Attribute("id"),
Owner = (string)photo.Attribute("owner"),
Title = (string)photo.Attribute("title"),
Secret = (string)photo.Attribute("secret"),
Server = (string)photo.Attribute("server"),
Farm = (string)photo.Attribute("Farm"),
}).Skip(pageIndex * columns * rows).Take(columns * rows);
int count = photos.Count();
if (pageIndex == 0)
{
this.prevAvail = false;
this.nextAvail = true;
}
else
{
this.prevAvail = true;
}
if (count < columns * rows)
{
this.nextAvail = false;
}
return photos;
}
catch (Exception ex)
{
return null;
}
}
}
}
PhotoInfo
using System;
using System.Collections.Generic;
using System.Text;
namespace Linq_3
{
public class PhotoInfo
{
private const string FLICKR_SERVER_URL = "http://static.flickr.com/";
private const string FLICKR_PHOTO_URL =
"http://www.flickr.com/photos/";
public string Id;
public string Owner;
public string Title;
public string Secret;
public string Server;
public string Farm;
public bool IsPublic;
public bool IsFriend;
public bool IsFamily;
public string PhotoUrl(bool small)
{
return FLICKR_SERVER_URL + this.Server + '/' +
this.Id + '_' + this.Secret + (small ? "_s.jpg" : "_m.jpg");
}
public string PhotoPageUrl
{
get { return FLICKR_PHOTO_URL + this.Owner + '/' + this.Id; }
}
}
}
So putting these three classes together we are able to come up with a little Flickr viewer that allows users to search Most Recent/Interesting or by Key Word, and allows the user to page through the results, if there is more than 1 page worth of photos. Nice huh?
As originally stated I have also included a WPF version of this XLINQ experiment, simply beacuse I am also learning WPF, and I think it looks cool. Some folks may also like to see how WPF can work with XML bound data, as is the case for this WPF application. Like I said earlier, if you don't get the WPF version, don't worry, that will be the focus of another article.
I hope there are some of you that have read Part1 and Part2 of this article series, and can see the similarities between all the different flavours of LINQ.
I am also hoping that this article has shown that XLINQ (Or LINQ over XML as it will be known in future) is not that scary, and its actually quite easy to use, even when using RSS and 3rd party XML data sources.
As a closing note on this series, I just want to tell people, I didn't have much knowledge about LINQ when I started, I just got my head down and went for it. Some of it is a little frustrating, but to be honest I managed to get all the demo apps doing what I wanted (not the WPF one, that is a different story) inside a single day, which is fairly good for a new technology I think. So say (or think) what you will, about LINQ, I think it is set to radically change the ways in which we work with data at every level. It may be that you will choose to only use LINQ, or only XLINQ or all that LINQ has to offer. I know that I will probably be trying out all three at some stage on a live system. Happy LINQing.
I would just like to ask, if you liked the article please vote for it, and leave some comments, as it allows me to know if the article was at the right level or not, and whether it contained what people need to know.
I have quite enjoyed constructing this article, and have been quite refreshed at just how easy XLINQ is to actually use. In fact I wish I had of done this XLINQ article 1st, as I recently finished a short term contract, where we were doing lots of XML parsing which would have been far easier with XLINQ. I also think the way in which XML data is updated is far superior in XLINQ to conventional methods, it just seems more logical the way its done in XLINQ. No more iterative processes, hooray.
v1.0 10/05/07: Initial issue
- Concepts behind the C# 3.0 language
- LINQ Project
- 101 LINQ Samples
- May 2006 LINQ CTP