In the first of our series of articles about how JobServe Labs is built, we’re going to look at how we’re using Windows Live Writer (WLW) to publish our articles, and how we’ve setup a blog service internally with WCF for WLW to talk to. If you’re planning a similar deployment, or if you’re looking for a way to build page-based content management for a website, then you might want to read through – as we point you in the direction of how to build it yourself.
Unlike other out-of-the-box blogging sites/engines, you will not find our blog endpoint on our website. The primary reason for doing this was to minimise attack vectors to the site – if you present a callable endpoint to the outside world then somebody will try to hack it. Anybody that could ever want to interact with the articles on our site will either be directly on our enterprise network, or connected to it remotely via a VPN.
The basic architecture, then, is:
The first stage was for us to decide which common blog functionality we wanted to support on our blog, and from there we could then determine which Blog spec we would code against. Using the Windows Live Writer default capabilities listings, we decided that we would use the MetaWebLog specification as our baseline, as it’s the closest to what we wanted. There were a couple of additional features we wanted to add, but these can be done using the <options> element of the wlwmanifest.xml file that our website presents to Live Writer – for those who are interested, here’s the complete listing from ours:
<options><clientType>Metaweblog</clientType><supportsPostAsDraft>Yes</supportsPostAsDraft><supportsFileUpload>Yes</supportsFileUpload><supportsCategories>Yes</supportsCategories><supportsCustomDate>Yes</supportsCustomDate><supportsCommentPolicy>Yes</supportsCommentPolicy><supportsExcerpt>Yes</supportsExcerpt><supportsNewCategories>Yes</supportsNewCategories><supportsNewCategoriesInline>Yes</supportsNewCategoriesInline><supportsMultipleCategories>No</supportsMultipleCategories><supportsHierarchicalCategories>No</supportsHierarchicalCategories><supportsKeywords>No</supportsKeywords><supportsPingPolicy>No</supportsPingPolicy><supportsAuthor>No</supportsAuthor><supportsSlug>No</supportsSlug><supportsPassword>No</supportsPassword><supportsTrackbacks>No</supportsTrackbacks><supportsPages>No</supportsPages><supportsPageParent>No</supportsPageParent><supportsPageOrder>No</supportsPageOrder><!--additional live writer options--><supportsEmptyTitles>No</supportsEmptyTitles><requiresHtmlTitles>No</requiresHtmlTitles><requiresXHTML>Yes</requiresXHTML><supportsScripts>Yes</supportsScripts><supportsEmbeds>No</supportsEmbeds><characterSet>UTF-8</characterSet><maxCategoryNameLength>40</maxCategoryNameLength><supportsAutoUpdate>Yes</supportsAutoUpdate></options>
If you’ve had any experience customising Live Writer from the manifest file, you might be wondering why we are specifying values that are the same as the defaults for the MetaWebLog spec. Part of it is to ensure that if Live Writer’s understanding of this spec changes in a future version, it’s understanding of our blog will not. It’s also to make it easier in the future should we wish to add capabilities – changing an existing value from No to Yes is simply faster than having to write the element in first.
An important point to note, if you’re going to develop your own blog engine like this, is that the MetaWebLog specification implies the Blogger specification as well, and WLW mixes it’s XML RPC calls between the two for the different features where the MetaWebLog RPC methods supersede the Blogger methods.
As an example, it will use the metaWeblog.editPost method instead of the blogger.editPost method when you re-publish an existing post; but it will use the blogger.deletePost method to delete a post because MetaWebLog doesn’t define an alternative method for doing so.
The next step was to decide how we were going to expose the blog to Live Writer – which is expecting an XML RPC endpoint to talk to. There is a very popular .Net Framework implementation of XML RPC, called XML-RPC.net, which we did look at, however we are moving all of our network coding over to WCF because your code is then not tied to a particular transport or protocol.
The problem with that is that WCF does not come with built-in message formatters and operation selectors compatible with XML RPC; so we were looking at writing our own. As we all know, however, as developers, we must always exhaust the community first to see if somebody else has done it, and to our joy we stumbled across a project by Clemens Vasters – a senior developer at Microsoft – where he’d been developing just such a thing for DasBlog. The zipped solution, which he talks about and links to on his blog, includes WCF operation selectors and formatters for XML RPC support in the same way that you write any other WCF hosted service (i.e using the ServiceContract attribute and OperationContract attribute). All we did was to take his ServiceHost setup code and move it to a core component that we could then host either in a console application (as in his example), which was great for development, or in a windows service for when we actually released it to the rest of the business.
Helpfully, he also includes WCF-contracted interface definitions (and the corresponding classes for their related structs) for Blogger, MetaWebLog and MovableType, as well as an in-memory implementation of his Blog Engine that runs from the command-line (which Live Writer will happily connect to). We didn’t use these directly, but if you’re following the same path as us, then you can go straight ahead and do so. Clemens stacks his interfaces up like this:
//define the interface for the actual blog api that will be called[ServiceContract]public interface IBlogAPI : IMovableType{}//define the interface for the IMovableType interface[ServiceContract(Namespace = "http://www.sixapart.com/developers/xmlrpc/")]public interface IMovableType : IMetaWeblog{//example[OperationContract(Action = "mt.setPostCategories")]bool mt_setPostCategories(string postid,string username,string password,Category[] categories /* MovableType category */ );// rest of the method definitions ommitted.}//define the interface for the IMetaWebLog interface[ServiceContract(Namespace = "http://www.xmlrpc.com/metaWeblogApi")]public interface IMetaWeblog : IBlogger{//example[OperationContract(Action="metaWeblog.editPost")]bool metaweblog_editPost(string postid,string username,string password,Post post, /* MetaWebLog post struct */bool publish);// rest of the method definitions ommitted.}//finally, define the base interface for IBlogger - the base API used by all[ServiceContract(Namespace = "http://www.blogger.com/developers/api/1_docs/")]public interface IBlogger{//example[OperationContract(Action="blogger.deletePost")]bool blogger_deletePost(string appKey,string postid,string username,string password,bool publish);// rest of the method definitions ommitted.}
One of the challenges is that many of the specifications share ‘struct’ definitions that are called the same thing, but which are subtlely different. The MetaWebLog Post struct, for example, is not the same as the Blogger Post struct. Clemens’ code namespaces each of the above interfaces and the classes that they require according to the blog API they implement. As a result, you will find yourself implementing some methods from different interfaces which use slightly different structs even if they have the same name. Thankfully, Windows Live Writer will always send the right struct version for the particular method it’s calling!
Now, to setup your real blog API, you simply declare a class that implements the root IBlogAPI (it’s actually IBloggerAPI in Clemens’ code – but that’s easy to confuse with the Blogger interface!), get Visual Studio to stub out all the interface methods you need, and then start coding against your database. We used Linq to Sql for our database interfacing layer because it’s really quick to get up and running, and is resilient to schema changes; it’s also hack-proof. Read through Scott Gu’s excellent series on using Linq to Sql.
We’re not going to go through hosting the service and the C# code required to do that – it’s all in Clemens’ project.
During development, we discovered a couple of minor bugs in the XmlRpcDataContractSerializer class – which we had to patch for our use. Unfortunately, we can’t make the amended version of the file available here as it’s got Microsoft’s Copyright on it, so we have sent our amendments to Clemens and hopefully he’ll patch the release that we’ve linked to above. To patch it yourself, open up the file XmlRpcDataContractSerializer.cs and make the following changes:
/* -----------------------------------------------------------------* Change 1: Line 65, adding else statement to if block that starts* if (dataMembers.ContainsKey(memberName))" on line 44:* Fixes: A crash that occurs if a struct field is sent that is* not part of the class being used to represent the struct in .Net* -----------------------------------------------------------------*/else{reader.Skip();}reader.ReadEndElement(); // valuereader.MoveToContent();reader.ReadEndElement(); // memberreader.MoveToContent();/*-------------------------------------------------------------------*//* ------------------------------------------------------------------* Change 2: Line 128, Replace case statement which deserialises* an XmlRpc DateTime value* Fixes: Crash when parsing DateTime values passed by Windows Live* Writer when setting an explicit publish date on an article.* XML RPC date format is not a .Net date time format.* ------------------------------------------------------------------*/case XmlRpcProtocol.DateTime:try{returnValue = Convert.ChangeType(reader.ReadElementContentAsDateTime(), targetType);}catch{string dateTime = reader.Value;//have to fix the current reader position (dirty fix).reader.Read();reader.Read();reader.Read();returnValue = XmlConvert.ToDateTime(dateTime, "yyyyMMddTHH:mm:ss");}break;/*-------------------------------------------------------------------*//* ------------------------------------------------------------------* Change 3: Line 249, replace the else-if* Fixes: This is the mirror fix to change 2 - ensures that outgoing* DateTimes are formatted correctly.* ------------------------------------------------------------------*/else if (valueType == typeof(DateTime)){writer.WriteStartElement(XmlRpcProtocol.DateTime);//modification here to write a 'proper' Xml RPC DateTime.if (value is DateTime)writer.WriteValue(((DateTime)value).ToString("yyyyMMddTHH:mm:ss"));elsewriter.WriteValue(value);writer.WriteEndElement();}/*-------------------------------------------------------------------*/
So now we have our blog service ready to go (assuming you’ve done your database and your XML RPC Endpoints are setup) the last thing we have to do is to tell Live Writer where our blog is, and what our pages look like.
Before you begin this bit, you should have a website that can display your blog posts – at a minimum this must have a root page which shows either all posts, or the most recent n posts – the top post should be the one that was posted most recently. Use this as your blog’s homepage from WLW’s point of view. Ours is our All Articles page.
For the best editing experience in WLW you should also have two additional Html Files, one for WebLayout view (editing view in WLW) and WebPreview view (the preview shown in WLW). The Live Writer Team have written guidance on MSDN as to how to make these two files – they use placeholders for the title and the article body – so the most basic version of these would simply look like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><body><h1>{post-title}</h1>{post-body}</body></html>
Live Writer simply substitutes the {post-title} and {post-body} macros for the content that it’s got from the current post – if the page requests CSS or Image files, then WLW will go and download them.
From our experience with this process, we also have a few suggestions and guidelines for you to follow regarding these files:
If your site has a graphical theme and funky layout (ours, for example uses a master page that puts the header and footer on, surrounds most content with a rounded border, and sticks a load of other links on), you’ll typically leave it off this page so as to maximise the default editing experience.
For example, this article looks like this in my editing view:
Note how the title is much larger than it appears on this page, and how there are no borders anywhere. The font and link styles, however, are maintained.
This one should be as close to the design used for the page that displays a single article (in this case the page that you’re looking at right now) – ours, for example is an aspx page that includes the master page and a lot of the extra HTML layout content.
Because you’re likely to be throwing all manner of CSS tricks at this page, it’s possible that it’ll not be identical to the actual web page; my preview, for example, takes a few liberties with the placement of our Feeds and Categories panels on the right hand side (WLW does a good job of it’s HTML compliancy, but it’s not perfect). However, the important thing is that it accurately displays the post inside the rounded panel surrounding the content:

Now that you have your preview and layout htmls, you can write your manifest file. From the information that we’ve mentioned so far, you’ve almost got what you need to do it. First, I’ll link again over to the Windows Live Writer Customisation API topic on MSDN, so you’ve got all the material to back you up.
Let’s assume that you are going to implement MetaWebLog exactly as is, with no customisations. Let’s also assume that your homepage is default.aspx, and that your WebLayout and WebPreview htmls are called ‘WebLayout.html’ and ‘WebPreview.html’. Your manifest file is going to look something like this:
<?xml version="1.0" encoding="utf-8" ?><manifest xmlns="http://schemas.microsoft.com/wlw/manifest/weblog"><!-- leave the options bit out, because we're sticking with whatlive writer uses as the defaults for MetaWebLog --><weblog><serviceName>My Blog</serviceName><homepageLinkText>http://my.domain.com/default.aspx</homepageLinkText></weblog><views><default>WebLayout</default><view type="WebPreview" src="/WebPreview.html"/><view type="WebLayout" src="/WebLayout.html"/></views></manifest>
As the MSDN documentation states, if you place this file in the root of your website and call it ‘wlwmanifest.xml’, then Windows Live Writer will be able to auto-detect it. If, however, you need to point Live Writer specifically at a particular file (we actually have two Blog endpoints for our site, one for our Articles and one for our Projects, so we need two manifests), then you use a <link> tag in the <head> section of the blog’s homepage. Here, it’s the ‘rel’ and ‘type’ attributes that do the magic:
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="MyWlwManifest.xml" />
With this in place, Windows Live Writer can auto-discover the manifest that you want it to use.
If you’re at this stage:
1) You’ve got a website that has a manifest file for Live Writer to download as per the last section – let’s say that the address is http://localhost/default.aspx for homepage.
2) You’ve got your blog API listening on an address on a local machine as per the section about Xml Rpc. Let’s say that’s on http://localhost:9999/blog/api.
Then you’re ready to go!
That should be it (other than confirming your blog’s name). If Windows Live Writer shows a message about making a temporary post in order to detect the theme of your site, then either it hasn’t picked up your manifest file, or it’s not been able to grab the WebPreview and WebLayout files referenced in the manifest. Review those URLs (ours use absolute paths to ensure that there can be no confusion) and try again (best to remove the account and add it again).
As with all situations where you have an application calling another by a remote mechanism, it’s nice to be able to see what is actually being sent to and fro. What we’ve done during development is to use Message Logging and Tracing in WCF (however, it’s not exactly real-time, because until your app shuts down it doesn’t actually sign-off the trace files, causing the Service Trace Viewer to throw errors) and to use Fiddler to debug the Http traffic travelling between Live Writer and your service. Unlike Internet Explorer, which automatically uses Fiddler when it starts up, you have to tell Live Writer to use your local proxy. There’s a setting in Tools –> Options –> Web Proxy which controls this; as soon as you change that you’ll start seeing your Xml RPC Payloads going to and fro.
There are a lot of content management systems out there, and they all offer a huge amount of features. One of the primary areas of distress for anybody wanting to roll out such a site, however, is the ability to get content, update it and post it again in a way that offers the most flexibility. By using a rich WYSIWYG offline editor like WLW you get a whole host of features immediately after installation, plus you also get a platform upon which you can build very quickly, thanks to its plug-in interface. We have a plug-in purely for sending up additional files (like the Vista Sidebar Gadget .gadget file) to the blog, because WLW only automatically uploads media content. Developing this took one day to achieve.
After hooking WLW into your blog, you’re then free to code the workflows behind the service that it uses. This post, for example, will be posted to our blog as a draft for another member of the team to open and review. They can use WLW to download the draft from it’s ‘Recently Posted’ feature. If that person is happy with that content, they can send it back to the blog as a published article simply by hitting the ‘Publish’ button.
We also have some role-based security behind the scenes, which hangs off of Active Directory, which means that only some people are allowed to actually publish the articles. The rest are restricted to posting drafts, which must then be ‘properly’ published by someone with permissions.
Connect: iPhone Job Search (8)
Connect: Mobile Job Search (1)
General (4)
Google Desktop Job Search (2)
iPhone (2)
Technical (7)
Web Browser OpenSearch Provider (1)
Windows Vista Sidebar Gadget (3)
Your Voice (1)