Friday, 3 July 2009
by
Aaron Powell
One of the topics that came out of the recent Umbraco CodeGarden 09 discussions was how we can go about supporting Umbraco communities outside of Europe. To this end we're going to be making an effort to get the community going in a much more consolidated effort. The primary focus is within Australia and New Zealand. If anyone is interested the notes from the open space about it can be found here on the wiki.
The first order of business is that we want to run a What you missed from CodeGarden 09 webinar.
Details are:
When - 6.30 pm (AEST) Monday 13th July 2009
Topic - What you missed from CodeGarden 09
Speakers - Aaron Powell (me!), Peter Gregory (New Zealand Umbracian) and (hopefully) Shannon Deminick
Where - Webex details will be provided closer to the date
Who - Everyone is welcome
How to register - Register your interest on the Umbraco Meetup forum topic (http://our.umbraco.org/forum/meetups-and-usergroups/suggest-a-meetup/2686-AUSPAC-Webinar) or contact me directly via me (at) aaron-powell dot com
See you there!
Wednesday, 1 July 2009
by
Aaron Powell
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<TDocType> method, this is responsible for the initial population of the collection from your data source.
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<TDocType> method:
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<TDocType> from the provided data.
Tree<TDocType>
This class is really just a wrapper for the IEnumerable<T> class. The way in which I have implemented the RssTree (and how I implemented the NodeTree) is by using delayed loading. What I mean is that the data isn't converted from the source to the result until the GetEnumerator() method is called.
This means that unless I do something with the collection there is no performance hit.
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<T> and then returning its Enumerator. The reason for this is you'll pick up a bit of performance if you are doing methods like Take(int) or breaking from a loop early.
You should probably push the items into an internal collection to support caching (which is what the Node implementation does), but this is just a quick demo.
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.
Tuesday, 30 June 2009
by
Aaron Powell
Well I'm back home in Australia after a fantastic trip over to Copenhagen Denmark for the Umbraco CodeGarden 09 conference and a whole bunch of drinking.
For the unlucky people who couldn't attend CodeGarden 09 here's some of the things you missed:
- My words of wisdom from the Retreat Podcast
- The new Umbraco community site, our.umbraco.org
- Umbraco 4.1 will be coming later this season (either summer or winter, depending where you are). The roadmap is up on the new wiki
- Umbraco 5.0 will be a brand new version with a MVC core (again, see the roadmap)
It looks like there are exciting times ahead :D
Wednesday, 17 June 2009
by
Aaron Powell
So I'm sitting here in the lovely Copenhagen Denmark preparing for a week long set of shinanigans which will end with Code Garden 2009.
The worst part so far was the 20 hours I had to spend on aircraft to get here (which I admit will suck just as much for the return trip!). Luckily I was able to con myself into the emergency exit row seating for both flights, which meant that I was given the much appreciated leg room and didn't suffer nearly as bad.
What did suck though was that on the first leg of the flight my inflight entertainment system froze so I was stuck having to read my books and watch videos on my iPod.
But at least the 777's have individual entertainment units, the 747's don't and that was for the longer of the two legs of my trip. I managed to squeeze in a few hours of sleep (not nearly as much as would really be a good idea...) but once I was awake to the point I knew I wouldn't get back to sleep I decided to make my own in-flight entertainment.
Well here it is, another sneak peek at LINQ to Umbraco, this time I'm showing off the Visual Studio integration.
Saturday, 13 June 2009
by
Aaron Powell
Ok, I'm going to go on a bit of a rant here.
I'm a Mac user, have been for nearly 2 years now and I think buying a Mac was one of the smartest moves I made as a Microsoft developer; I can run virtual machines for everything I do, easily swapping between different OSes. I currently have XP, Vista and Win7 all on my Mac so I can easily test IE6 -> IE8 without any hacks.
But Apple can really piss me off some times. This week has seen WWDC going on in the US and by-and-large I've been underwhelmed by it, almost to the point where I'm just plain annoyed at Apple and their marketing attitude.
And today I read an article which really got me angry, Apple announced that Safari 4 has had 11 million downloads. Now if you read that article you'd think that Safari has become the most dominate browser on the planet. I mean, Safari has always been a bit-player running along behind the likes of IE and Firefox like the annoying yappy little dog trying to be one of the big fellows.
You know what, nothing has changed.
What Apple have failed to mention in their announcement is that Safari 4 is pushed out to all OS X 10.5 (and 10.6 beta users I guess) as a system update, and at that, a pre-selected system update.
So I've installed Safari 4, but I use it maybe 1% of my browsing time (I'm one of those really horrible people who uses an even more obscure browser, Opera :P), but hey, I'm part of the 11 million strong loyal Safari fan-base.
Sure Microsoft probably has IE8 listed as an update for Vista (work domain policy just auto-updates for me, I don't ever look at them) but I remember when IE7 was released for XP it wasn't selected-by-default as an install, it was an optional. Sure Safari 4 was an optional upgrade, but when something is optional by opt-out rather than optional by opt-in you find more people will not bother to opt-out.
Using Apple's logic we can say that 20 million people think Kevin Rudd is a good prime minister. After all, the population of Australia is 20 million, he's the PM and to the best of my knowledge there isn't any kind of coup d'état planned to remove him from power.
As the great Homer Simpson said "People can come up with statistics to prove anything. 14% of people know that".
Oh, and to close off, this post was written in Safari 4 on OS X ;).
Monday, 8 June 2009
by
Aaron Powell
This post will be looking at another problem I had to overcome with creating the SFG for LINQ to Umbraco, this time I'll look at how to do an installer while using wild-card version numbering.
I'm a big fan of wild-card versioning of assembliies, and if you're not familiar with what I'm taking about I'm referring to when you make the assembly version number end in a * within the AssemblyInfo.cs file, eg:
[assembly: AssemblyVersion("0.0.1.*")]
So what's the point of this? Well I find it useful when you're doing development and releases to know exactly where you're at. There's nothing worse than going to update an environment, but then you find out that the same version is already there. Was it because the developer hadn't incremented the version number, or did I miss the deployment?
By using wild-card versioning the numbers for the * are generated by the compiler on the fly. I'm not 100% sure how it can it is generated, I got a little bit of information from a developer on the VS core team stating:
When versioning as #.#.x.y x is generated as the number of days since some time in 2000 (which you can work out fairly easily) and y is the number of seconds since midnight on the date divided by 2.
Pretty sweet but it means that knowing the version number before compile time is next to impossible. This makes a real problem when doing the CLSID registry key which I talked about in Part 1 of this series.
So how do we get around this? Well the obvious way is to make a fixed version number, but as I stated I don't like doing that, particularly when I'm doing the development, I'm really wanting to know what version of the generator is running vs what version I last compiled.
Well, how do we get around it?
Introducing System.Configuration.Install.Installer (System.Configuration.Install)
We need to make a class which inherits from the Installer class. This will allow us to run custom .NET code during the installer which we can do what we want, like say, edit the registry.
There are two methods which should be overriden, Install and Uninstall. If we're modifying the registry we need to write our own custom code to undo those changes. No one likes it when software doesn't clean up after itself.
Because we're going to need to know the version of the assembly the best place to add this is within the same assembly as the SFG itself.
Registry editing 101
There's lots of good tutorials on editing the registry with .NET so I'll just cover the basics. First off you need to create the generator key within the CLSID key:
RegistryKey genKey = Registry.LocalMachine.CreateSubKey(@"Software\Microsoft\VisualStudio\9.0\CLSID\{52B316AA-1997-4c81-9969-95404C09EEB4}");
Next we need to create the values for the registry key via genKey.SetValue(name, data). So it will look like this:
genKey.SetValue("Assembly", Assembly.GetExecutingAssembly().FullName);
genKey.SetValue("Class", "Umbraco.Linq.DTMetal.CodeBuilder.LINQtoUmbracoGenerator");
genKey.SetValue("InprocServer32", Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), @"system32\mscoree.dll"));
genKey.SetValue("ThreadingModel", "Both");
genKey.Close();
Since we need to get the full name of the assembly, including version number (which we don't know!) we can just use reflection against the current assembly. And also we don't have the Installer environment variables so we need to map the path ourselves. And once done close off the registry.
Wrapping It Up
To finish it up we need to add an attribute to our custom installer class:
[RunInstaller(true)]
And then edit the Custom Actions section of the Windows Installer project and specify that the output of the SFG assembly will be used in both the Install and Uninstall steps.
Conclusion
So this is how you go about doing a SFG with wild-card assembly versioning and setting the registry up correctly.
Monday, 8 June 2009
by
Aaron Powell
LINQ to Umbraco is trucking along brilliantly and I recently solved a really big problem that I had, creating a Single File Generator (SFG).
For anyone who's not familiar with a SFG it is a tool for Visual Studio which allows a document to have .NET code generated for it when the file is saved (or the custom tool is explicitly run). There's a very good example as part of the Visual Studio 2008 SDK which covers how to create a SFG (here is the documentation for it).
The most familiar SFG people will know is the one used by LINQ to SQL or Entity Framework.
The above linked document (and the SDK example) are great for explaining how to create the SFG, but there is something which it doesn't cover, how do you provide a redistributable for it?
This was a major problem that I was having, I couldn't work out the best way to achieve it. Luckily I came across a project which showed me how it was to be done, in the form of LINQ to SharePoint. There are a few registry keys that need to be inserts in the right places, and if it wasn't for LINQ to SharePoint I wouldn't have been able to find anywhere which explained it.
First off you need to have an Installer project, and I'm going to make the assumption that that has been done and that the DLL's you want deployed are linked in already.
Registry Keys
The keys need to be added with HKEY_LOCAL_MACHINE (HKML) and the first one is in a key called AssemblyFolderEx. The full path we'll be creating the key is HKLM\Software\Microsoft\.NETFramework\v3.5\AssemblyFolderEx. In this key you'll need to create a new key, which has the name of the SFG (eg: LINQtoUmbracoGenerator) and it has a default value of [TARGETDIR], which is a variable from within the installer.
I'm not 100% sure of the point of this registry key, nor if it's actually required. Better safe than sorry in my opinion though :P
Now we need to create the registry keys within Visual Studio to activate the SFG, there's actually 2 - 3 (depending what languages you support) that need to be created.
The CLSID Key
The CLSID key is used to define assembly, class and some other data about your SFG. This key will reside in HKLM\Software\Microsoft\VisualStudio\9.0\CLSID\. In this registry key you need to create a new key which uses the GUID of your generator class as it's name. So for LINQ to Umbraco I ended up with a key like this:
HKLM\Software\Microsoft\VisualStudio\9.0\CLSID\{52B316AA-1997-4c81-9969-95404C09EEB4}
Inside this key we need to create the following (all String values):
- Assembly
- Full name of the assembly (including version, public key, etc)
- Class
- Full name of the class of the SFG
- InprocServer32
- [SystemRoot]\system32\mscoree.dll (not quite sure what this is for)
- ThreadingModel
- Both (again, don't really know what it's for)
Now the CLSID is set up for the generator so Visual Studio will be aware of where the class to invoke resides.
The Language Generators
Although the CLSID is set up you need to set the generator names for the language(s) you are supporting. LINQ to Umbraco supports C# and VB.NET so I'll point out both in here.
All the installed SFG's are kept under a single key within the registry, which is HKLM\Software\Microsoft\VisualStudio\9.0\Generators. If you look at this within your registry there will be a number of different GUID's (changing depending on what Visual Studio languages you have installed).
For VB.NET you need to create under the {164B10B9-B200-11D0-8C61-00A0C91E29D5} key, and for C# place under {FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}.
Under the language keys you need to create a new key with the name of your generator (eg: LINQtoUmbracoGenerator) with the following values:
- (Default) - String
- Friendly name of your generator (eg, VB LINQ to Umbraco Generator)
- CLSID - String
- GUID (including {}) of the CLSID defined earlier
- GeneratorDesignTimeSource - DWORD
- 1 if you want to generate on save (I think!)
Replicate that under each of the language you want to support.
Conclusion
So that concludes part 1, the registry keys are the most frustrating part, but once they are working it's such a relief. If the above was confusion (which I'm not doubting it was) I'd suggest you grab a copy of the LINQ to Umbraco source from CodePlex and just look at what is setup in there.
Update: Just realised I had a registry key wrong. It should have been HKLM\Software\Microsoft\v3.5 not HKLM\Software\Microsoft\3.5
Saturday, 6 June 2009
by
Aaron Powell
I was on StackOverflow yesterday and I was reading a post about the strangest programming language you've ever used. While looking at what people have used I realized I haven't worked with anything that strange.
But then I was thinking there is one language I used that's a bit strange, JavaScript.
Without going into all the weirdness of the JavaScript language I'd like to focus on one bit craziness which I'm quite fond of, self executing recursive anonymous functions. Yeah it's a bit of mouthful but it's also a bit of fun, and may even have some practical uses.
We're all familiar with JavaScripts ability to do anonymous functions, they are often used within event delegates and and constantly used when doing jQuery. Something like this:
jQuery('#button').click(function() { ... } );
So that's the anonymous part of what we're trying to achieve, now lets look at self executing functions.
JavaScript can do self executing functions, they are generally used for creating objects. jQuery is in fact an example of this, which is why if you do a typeof jQuery you get function as the response. For example:
var result = (function() { ... })();
Notice the () at the end, this tells the function to execute and take no parameters. But you can also do this:
var result = (function(node) { ... } )(document.getElementById('button'));
I'm not going to cover what a recursive function is, I'm sure we all know what they are, but I did raise a problem, we're using anonymous functions, how do I call a function without a name?
Well JavaScript actually has a way of doing this, every JavaScript function has a hidden parameter called arguments, this is a collection of all the arguments passed into the function in the order they were passed in. So you can do something like this:
(function() {
for(var i = 0; i < arguments.length; i++) {
alert(arguments[i]);
}
})("hello", "world");
This will do two alerts, the first saying hello the second saying world. But there's another property on the arguments object, arguments.callee. This is a reference to the method which called the current function. And because it's a reference to the function we can have some real fun, because you can execute arguments.callee!
Say I wanted to know if a node as a child of a node with a particular ID, I can do this:
var isChild = (function(node) {
if(node) {
if(node.id === 'parent') {
return true;
} else {
return argument.callee(node.parentNode);
}
} else {
return false;
}
})(document.getElementById('child'));
How nifty! Ok, yeah it does make the function a lot less reusable, but hey, this was an example of craziness of the JavaScript language! Oh, and I have used this before, see my post Creating jQuery plugins for MS AJAX components, dynamically!
And this is why JavaScript is the strangest language I have ever used.
Monday, 1 June 2009
by
Aaron Powell
I've been doing a lot of playing with testing frameworks and working out what's the best to use for the different needs. There's two kinds of frameworks out there for .NET, mocking frameworks and isolation frameworks.
There are different reasons for using the different framework types and I'm to try and explain which one is a good choice for what you're trying to do.
What is mocking?
Mocking is the concept of producing fake versions of the objects you want to operate with. With these fake versions you then are able to specify how they operate, what their methods will return, etc.
There's quite a few frameworks available for mocking, RhinoMocks, Moq and NMock to name a few. These are all open source projects and they are all very good. The each offer very similar features, the kind of features which are expected by developers such as:
- Mocking properties and methods
- Expecting calls
- Asserting execution paths
Mocking frameworks are best when you've got full control over the components being used, or the components used are prebuilt with mocking in designed.
What is isolating?
Isolating is similar to mocking, but it is much more broadly focused, with the idea that you make fake versions of everything, regardless of whether you developed the component or not.
This is why isolating frameworks are becoming popular with hard-to-mock components such as CMS cores, ASP.NET or Silverlight.
When it comes to isolation frameworks in .NET Typemock is one of the biggest players. Their framework is well designed to do testing SharePoint, ASP.NET (via Ivonna) and others. But it's quite possible to use Typemock to mock out other systems such as the .NET framework (with the limitation of mscorlib, but that's changing!) or other CMS's such as Umbraco.
What makes mocking different to isolation?
So now that we've got a bit of a background on mocking and isolating what's the different between the two, why would you use Typemock which isn't free over RhinoMocks which is?
Well it really comes down to what you're trying to do, mocking frameworks are only useful when the project is designed for mocking, where as isolating can be done more after the fact.
To understand what I mean by this you really need to understand how the framework types work. Most of the free mocking frameworks are built on top of the DynamicProxy which is used for dynamically generating the classes. This is how the mocking frameworks operate, an implementation of the class is dynamically created. This is why working with mocking frameworks really require the code to be designed for mocking. If your class is sealed, or your method is non-virtual it is no longer able to be mocked. Because of how DynamicProxy works it implements the class with the rules specified, but if it's sealed, it can't have an implementation done. Same with non-virtuals, if an override can't be performed there is no way to add your own rules.
Typemock's Isolator on the other hand uses black magic to achieve what it does. Ok, well not black magic but close, I'm not really privileged to it's operation, but from my understanding it uses a profiler to analyze the execution path and then creates the rules specified in raw IL and inject that. This means that the restraints of DynamicProxy no longer apply. Since the IL is being injected on-the-fly anything can be faked. Sealed classes, non-virtuals, even objects without public constructors!
Which to use when?
So which should you be using and when? Well mocking is great when you're starting a new project, when you've got ground up control over what's being developed. Making something that is 100% mockable is a very difficult task though, it requires a lot of design though, and ensuring that all data required for an operation is either passed is or available on the base object.
This can lead to what I consider lax design, particularly when you're developing a framework of your own. Because everything has to be unsealed and virtual it can lead to undesirable extensibility.
I'm from the school of thought that classes should be sealed-by-default. If something is to be extended I'll design it for extensibility, and if I don't want it extended or don't think it should be extended I wont make it available for extension.
And here is where isolation frameworks come in, they allow for this kind of design. Because they don't require the classes or methods designed for extensability it means tighter design but testing still achievable.
Additionally it does mean that it's possible to fake out systems you have no control over, such as a CMS which are inheritly untestable.
Some people are concerned about this kind of faking power, that you're possibly making assumptions of how an external system will operate which may be incorrect. But if that is the case then you're placing too much on the unit tests, without having any integration tests to back up the assumptions.
Conclusion
Hopefully this has shed some light onto the world of mocking and isolating. But really the best way to work out what's right for you is to grab a copy and get coding!
Saturday, 30 May 2009
by
Aaron Powell
One of the goals of LINQ to Umbraco is to be able to have Umbraco applications which are done without a web context, starting to using Umbraco as a service.
Now there's been plenty of ways to do this in the past, you can quite easily have a web service which pushes out the data you require, but I wanted to do it entirely without web stuff.
With Code Garden coming up in a few weeks I decided to have a look into writing something to show off the concept I was thinking of, that you could write a WPF application which is entirely driven from Umbraco content.
When reading a recent blog post from Scott Hanselman in which he talks about a tool he uses when doing presentations which reads Twitter hash tags and he can get audience feedback. So I thought, why not do that, but using Umbraco for the messages rather than Twitter.
Enter the Umbraco Notifier
So my concept was decided, you would have a small WPF app that sits in the system tray and check an Umbraco XML file for changes.
To go along with that I would have an Umbraco instance running which has a simple web form that people can submit their notification to me.
Now I'm not going to give away the code here, thats a secret for CG, but pictures are worth 1000 words, so how many words is a screen cast worth? Follow the link to see the notifier in action!
You'll probably want to turn your computer speakers off, I'm still learning how to use the software and the background noise is well... backgroundy :P.
So there you have it folks, an Umbraco driven WPF application, with LINQ to Umbraco in full operation.
Next >