Setting Up Your Website to Work With Windows Live Writer

Windows Live Writer, for those of you that aren't aware of the application, is an application to help you do posts, upload images/video, and manage a blog/website. It's one of the better apps of it's type out there and has quite a few addins that really make using it worthwhile (although you're definately going to want to download a file uploader addon). On top of that, WLW works with most of the blogging platforms out there (Blogger, Wordpress, BlogEngine.Net, etc.).

So it sounds great, but what happens when you're creating your own blogging platform and want to add integration? You turn to an API called MetaWeblog (well that and portions of the Blogger API but I'll mention that in a second). MetWeblog is an API based on XML-RPC. It was designed to allow outside apps to get the text of blog posts and modify them or do new posts. Actually that's not 100% true. We had the Blogger API that did that, but it had a number of flaws. The MetaWeblog API was designed to add that functionality that was lacking. That also explains why we're going to use both of the APIs in order to hook up Windows Live Writer to our site.

What do I need to set up?

Anyway, MetaWeblog has a number of functions that it defines in order to accomplish what it needs to do:

  • newPost - adds a post
  • editPost - edits a post
  • getCategories - gets a list of categories
  • getPost - gets a post
  • getRecentPosts - gets a list of recent posts
  • newMediaObject - used to upload a file/object

On top of that we need a couple functions from the Blogger API:

  • deletePost - deletes a post
  • getUserInfo - gets a user's info
  • getUsersBlogs - gets a user's list of blogs (there may be more than one)

Now there are other APIs that you can add to this list (WordPress, etc.) but these are the main functions that you'll most likely use. So now that we know what we need to implement, we need to know how.

How do I set it up?

The application really only requires a couple of things:

  1. RSD
  2. WLW Manifest
  3. Handler for implementing the functions

The RSD (Really Simple Discovery) is something that I've talked about before and won't go over in great detail here. The one item that you need to add to the APIs list is a single entry:

<api name="MetaWeblog" preferred="True" apiLink="http://SERVERNAME/MetaWeblogHandler.ashx" blogID="http://SERVERNAME/"/>

That entry lets Windows Live Writer know that it can connect to our handler at MetaWeblogHandler.ashx. After we set up our RSD file, we need to set up our WLW Manifest. The WLW Manifest lets Windows Live Writer know what it can do. The manifest is simply an XML file that can be found at http://www.MYSERVERNAME.com/wlwmanifest.xml. There are a lot of different options and looking at this link will tell you what they do. But just a default file is going to look like this:

<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns="http://schemas.microsoft.com/wlw/manifest/weblog">
<options>
<clientType>Metaweblog</clientType>
</options>
</manifest>

This just lets it know to use the Metaweblog defaults, but you can go in and change it to your hearts content (although that means you'll have to add the functionality). So that's steps 1 and 2. So what about step 3? To be honest, doing this yourself is a pain (mainly because you have to implement XML-RPC). Thankfully someone was nice enough to write a library for us: XML-RPC.Net.  You're going to have a couple issues compiling it since they don't include a signed key, but just add your own and you're good to go.

So once you download that library and get it set up, we have very little left to do and really it's only a couple of steps:

  1. Add the DLL as a reference within our Web App (note that I use Web Apps and not Web Sites, so I'm going to explain based on that)
  2. Copy over the interfaces for Blogger and MetaWeblog from the interfaces directory of the XML-RPC.Net library.
  3. Create a new handler
  4. Have the handler inherit from the two interfaces as well as XmlRpcService
  5. Go through and implement the functions that are listed above
  6. Add the handler to your web.config file

So basically what you'll end up with is something that will look like this:

    public class MetaWeblogHandler:XmlRpcService,IMetaWeblog,IBlogger
    {
        #region Constructor
        public MetaWeblogHandler()
        {
        }
        #endregion

        #region IMetaWeblog Members

        public object editPost(string postid, string username, string password, CookComputing.MetaWeblog.Post post, bool publish)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                    //edits a post
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        public CategoryInfo[] getCategories(string blogid, string username, string password)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                    //Gets the list of categories
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        public CookComputing.MetaWeblog.Post getPost(string postid, string username, string password)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                    //Get a single post
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        public CookComputing.MetaWeblog.Post[] getRecentPosts(string blogid, string username, string password, int numberOfPosts)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                    //Get a list of recent posts
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        public string newPost(string blogid, string username, string password, CookComputing.MetaWeblog.Post post, bool publish)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                    //add a new post
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        public UrlData newMediaObject(string blogid, string username, string password, FileData file)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                    //Add the item to your site
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        #endregion

        #region IBlogger Members

        public bool deletePost(string appKey, string postid, string username, string password, bool publish)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                    //delete the post
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        public object editPost(string appKey, string postid, string username, string password, string content, bool publish)
        {
            throw new NotImplementedException();
        }

        CookComputing.Blogger.Category[] IBlogger.getCategories(string blogid, string username, string password)
        {
            throw new NotImplementedException();
        }

        public CookComputing.Blogger.Post getPost(string appKey, string postid, string username, string password)
        {
            throw new NotImplementedException();
        }

        public CookComputing.Blogger.Post[] getRecentPosts(string appKey, string blogid, string username, string password, int numberOfPosts)
        {
            throw new NotImplementedException();
        }

        public string getTemplate(string appKey, string blogid, string username, string password, string templateType)
        {
            throw new NotImplementedException();
        }

        public UserInfo getUserInfo(string appKey, string username, string password)
        {
            throw new NotImplementedException();
        }

        public BlogInfo[] getUsersBlogs(string appKey, string username, string password)
        {
            try
            {
                if (System.Web.Security.Membership.ValidateUser(username, password))
                {
                     //Return the user's blogs
                }
            }
            catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
            throw new XmlRpcFaultException(0, "User name or password is invalid");
        }

        public string newPost(string appKey, string blogid, string username, string password, string content, bool publish)
        {
            throw new NotImplementedException();
        }

        public bool setTemplate(string appKey, string blogid, string username, string password, string template, string templateType)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

Obviously with the code bits fleshed out (and the validation code should be whatever it needs to be). But that's all there is to it. So try setting it up, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 3/23/2009 at 11:18 AM
Tags: , , ,
Categories: ASP.Net | C# | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

RSD Helper in C#

I guess I should start out by saying what the RSD format is and what it's used for... One of the main issues that developers have had is the inability to really connect to and interact with web sites without knowing intimate info about the way that site was set up. So eventually, someone who was annoyed with that fact, came up with a file format called RSD. RSD stands for Really Simple Discovery. RSD has one purpose, to let applications know what services are available on a website and where they are located. And while the format is simple, I decided to create a couple classes to help out.

RSD.zip (4.30 kb)

There are only three classes with minimal code. The basic RSD file is really just an XML doc that consists of only a couple fields. Specifically the file has the name and link to the engine that created the RSD file, the link to your website/blog, and the list of APIs. The APIs themselves only contain the name, whether it is preferred (so a client app knows which item to try first), the link to the API, and the blog ID (usually just the link to the blog and can even be empty). That's it really. It would be more interesting if there were a couple fields that needed explaining but it's really simple (go figure considering the name).

Now finding an RSD file takes a bit more work. For that, you need to either look in the header of the web page/site to find a link tag like the following:

<link rel="EditURI" type="application/rsd+xml" title="RSD" href="Link to the RSD file" />

And if that isn't there, the default is http://www.whatever the website is.com/rsd.xml... And if it isn't there, there is no RSD file (or at least not one that you're going to find). Anyway, try out the code, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 3/19/2009 at 10:56 AM
Tags: ,
Categories: C# | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

BlogML Loader in C#

This is similar to my RSS loader, but is really only designed to load a BlogML file (it's still a work in progress). Anyway, for those of you that have no idea what BlogML is, it's an XML format designed to export/import content from a blog. It has the ability to contain most of the information within your blog with some, specific holes. But in those cases, you can usually copy the info or simply don't need it.  All of that being said, you'll probably never need to use the format. And even if you do need to use it, most blog engines out there support it or someone has built something to help you convert from the format to something you can use. However, in my case, I have to be able to load the information from this blog into another source that doesn't support it. I could use the default code on the main site but I felt like creating one myself. Thankfully I didn't need to create an exporter as well, since BlogEngine.Net supports BlogML. As such, all I had to do was to create a loader.

BlogML.zip (12.04 kb)

The zip file contains a bunch of files (BlogML.cs is the main file used to load everything). And as I said before, it's still a work in progress as I still need to add functionality for exporting it back out, some of the fields. that I didn't really need, etc. When I finish that, I'll put it out within the utility library. Hopefully this code, though will help someone out. So give it a try, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 3/17/2009 at 9:15 AM
Tags: ,
Categories: C# | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed