Behind LINQ to XML and the Event Log generator by ThinqLinq

Behind LINQ to XML and the Event Log generator

Yesterday, I posted my "cool code" example which extracted information from the event logs and presented them as a RSS document using Linq to XML (XLINQ). At that point, I didn't have time to explain the code. Today, I will deconstruct the code to help explain how it works.

As a recap, here's the "one line of code" that handles the task:

If EventLog.Exists(LogName) Then _
Response.Write(<channel>
<title>Event Log from <%= Environment.MachineName %></title>
<description>Events logged in the system's <%= LogName %> Event Log</description>
<copyright>This RSS feed is copyright (c) <%= Now.Year.ToString %>.</copyright>
<publisher>Your Name</publisher>
<author>you Lastname</author>
<language>en-US</language>
<%= From entry In New EventLog(LogName).Entries.Cast(Of EventLogEntry)() _
Where (FilterType Is Nothing OrElse FilterType.ToUpper = entry.EntryType.ToString.ToUpper) And _
(EventSourceName Is Nothing OrElse EventSourceName.ToUpper = entry.Source.ToUpper) _
Select <item>
<title><%= entry.EntryType.ToString() & ": " & entry.Source %></title>
<guid isPermaLink="false"><%= Environment.MachineName & "/" & LogName & "/" & entry.Index %></guid>
<description><%= GetItemText(entry) %></description>
<pubDate><%= entry.TimeWritten.ToUniversalTime().ToString() %></pubDate>
<date><%= entry.TimeWritten.ToString() %></date>
<creator><%= entry.UserName %></creator>
</item> %>
</channel>)

To begin understanding what is going on, let's take a look at the XML literals in VB9. The main advance for XML in the next version of VB (and why VB 9 is cooler than C# 3.0) is the ability to declaritivly create XML elements using inline literals. Thus we can simply create an XML document in VB 9 with the following syntax:

Dim foo as XElement = _
<parent>
<child>this is some content</child>
</parent>

Given this we can easily start constructing the RSS envelope as follows:

Dim rss as XElement = _
<channel>
<title></title>
<description></description>
<copyright></copyright>
<publisher></publisher>
<author></author>
<language></language>
</channel>

With that format, we simply insert our content inline inside of the elements as normal. If we want to perform some code action and insert function results or variables inside of an element, we simply isolate it with the <%= %> tags familiar to ASP.Net developers. Thus we can include the Machine's name as part of the title element using the following syntax:

<title>Event Log from <%= Environment.MachineName %></title>

VB 9 doesn't limit us to simple function calls, but also allows us to declare procedural looping or other code segments, including LINQ queries. In this way, we are able to create our <item> collection by iterating through the list of events. Since LINQ allows us to easily iterate through list elements, it is the perfect vehicle for declarativly creating our items. Thus with LINQ, we would be able to use the following query:

From entry In New EventLog(LogName).Entries.Cast(Of EventLogEntry)() _
Where (FilterType Is Nothing OrElse FilterType.ToUpper = entry.EntryType.ToString.ToUpper) And _
(EventSourceName Is Nothing OrElse EventSourceName.ToUpper = entry.Source.ToUpper) _
Select entry

In our code, we have acquired the FilterType and EventSourceName values from the query string. (See the code download for the implementation details). One detail that should be mentioned here relates to the .Cast extension method. LINQ query operators are able to work on any object that implements IEnumerable(Of T). However, the EventLog(LogName).Entries object only implements IEnumerable rather than the strongly typed generic version. Luckily with the MAY CTP, they added the Cast(Of T) extension method which converts the Untyped IEnumerable into a strongly typed IEnumerable(Of T). With this method, we can create an IEnumerable(Of EventLogEntry) rendition from the .Entries list.

Now that we can iterate over our collection, we need to project the results into XML Element nodes (XElements). This is just as easy to do as it is to create the RSS envelope earlier. Here for our select projection, we replace the simple "Select entry" with a more complex XML projection:

Select <item>
<title><%= entry.EntryType.ToString() & ": " & entry.Source %></title>
<guid isPermaLink="false"><%= Environment.MachineName & "/" & LogName & "/" & entry.Index %></guid>
<description><%= GetItemText(entry) %></description>
<pubDate><%= entry.TimeWritten.ToUniversalTime().ToString() %></pubDate>
<date><%= entry.TimeWritten.ToString() %></date>
<creator><%= entry.UserName %></creator>
</item>

Once again, we are able to inject the results of CLR functions, including Environment.MachineName; or our custom methods like GetItemText(entry). Into child nodes. Additionally, by placing our entire query (from...select) inside of the <%= %> region we can easily insert all of the <item> elements inside the body of our <channel> node.

Once we have our XML item constructed, it is extremely easy to serve the resulting XML up to a consumer. All we need to do is call the .ToString method on the XElement. Since it is the default, we can simply just Response.Write out the results of the XLINQ created document and walla, an RSS feed of the event logs in "one [REALLY LONG] line of code."

Posted on - Comment
Categories: Linq to XML -
comments powered by Disqus