Setting Up Your Website to Work With Windows Live Writer

3/23/2009

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:

   1: <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:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <manifest xmlns="http://schemas.microsoft.com/wlw/manifest/weblog">
   3: <options>
   4: <clientType>Metaweblog</clientType>
   5: </options>
   6: </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:

   1: public class MetaWeblogHandler:XmlRpcService,IMetaWeblog,IBlogger
   2: {
   3:     #region Constructor
   4:     public MetaWeblogHandler()
   5:     {
   6:     }
   7:     #endregion
   8:  
   9:     #region IMetaWeblog Members
  10:  
  11:     public object editPost(string postid, string username, string password, CookComputing.MetaWeblog.Post post, bool publish)
  12:     {
  13:         try
  14:         {
  15:             if (System.Web.Security.Membership.ValidateUser(username, password))
  16:             {
  17:                 //edits a post
  18:             }
  19:         }
  20:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
  21:         throw new XmlRpcFaultException(0, "User name or password is invalid");
  22:     }
  23:  
  24:     public CategoryInfo[] getCategories(string blogid, string username, string password)
  25:     {
  26:         try
  27:         {
  28:             if (System.Web.Security.Membership.ValidateUser(username, password))
  29:             {
  30:                 //Gets the list of categories
  31:             }
  32:         }
  33:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
  34:         throw new XmlRpcFaultException(0, "User name or password is invalid");
  35:     }
  36:  
  37:     public CookComputing.MetaWeblog.Post getPost(string postid, string username, string password)
  38:     {
  39:         try
  40:         {
  41:             if (System.Web.Security.Membership.ValidateUser(username, password))
  42:             {
  43:                 //Get a single post
  44:             }
  45:         }
  46:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
  47:         throw new XmlRpcFaultException(0, "User name or password is invalid");
  48:     }
  49:  
  50:     public CookComputing.MetaWeblog.Post[] getRecentPosts(string blogid, string username, string password, int numberOfPosts)
  51:     {
  52:         try
  53:         {
  54:             if (System.Web.Security.Membership.ValidateUser(username, password))
  55:             {
  56:                 //Get a list of recent posts
  57:             }
  58:         }
  59:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
  60:         throw new XmlRpcFaultException(0, "User name or password is invalid");
  61:     }
  62:  
  63:     public string newPost(string blogid, string username, string password, CookComputing.MetaWeblog.Post post, bool publish)
  64:     {
  65:         try
  66:         {
  67:             if (System.Web.Security.Membership.ValidateUser(username, password))
  68:             {
  69:                 //add a new post
  70:             }
  71:         }
  72:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
  73:         throw new XmlRpcFaultException(0, "User name or password is invalid");
  74:     }
  75:  
  76:     public UrlData newMediaObject(string blogid, string username, string password, FileData file)
  77:     {
  78:         try
  79:         {
  80:             if (System.Web.Security.Membership.ValidateUser(username, password))
  81:             {
  82:                 //Add the item to your site
  83:             }
  84:         }
  85:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
  86:         throw new XmlRpcFaultException(0, "User name or password is invalid");
  87:     }
  88:  
  89:     #endregion
  90:  
  91:     #region IBlogger Members
  92:  
  93:     public bool deletePost(string appKey, string postid, string username, string password, bool publish)
  94:     {
  95:         try
  96:         {
  97:             if (System.Web.Security.Membership.ValidateUser(username, password))
  98:             {
  99:                 //delete the post
 100:             }
 101:         }
 102:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
 103:         throw new XmlRpcFaultException(0, "User name or password is invalid");
 104:     }
 105:  
 106:     public object editPost(string appKey, string postid, string username, string password, string content, bool publish)
 107:     {
 108:         throw new NotImplementedException();
 109:     }
 110:  
 111:     CookComputing.Blogger.Category[] IBlogger.getCategories(string blogid, string username, string password)
 112:     {
 113:         throw new NotImplementedException();
 114:     }
 115:  
 116:     public CookComputing.Blogger.Post getPost(string appKey, string postid, string username, string password)
 117:     {
 118:         throw new NotImplementedException();
 119:     }
 120:  
 121:     public CookComputing.Blogger.Post[] getRecentPosts(string appKey, string blogid, string username, string password, int numberOfPosts)
 122:     {
 123:         throw new NotImplementedException();
 124:     }
 125:  
 126:     public string getTemplate(string appKey, string blogid, string username, string password, string templateType)
 127:     {
 128:         throw new NotImplementedException();
 129:     }
 130:  
 131:     public UserInfo getUserInfo(string appKey, string username, string password)
 132:     {
 133:         throw new NotImplementedException();
 134:     }
 135:  
 136:     public BlogInfo[] getUsersBlogs(string appKey, string username, string password)
 137:     {
 138:         try
 139:         {
 140:             if (System.Web.Security.Membership.ValidateUser(username, password))
 141:             {
 142:                  //Return the user's blogs
 143:             }
 144:         }
 145:         catch { throw new XmlRpcFaultException(0, "An error occurred while loading information"); }
 146:         throw new XmlRpcFaultException(0, "User name or password is invalid");
 147:     }
 148:  
 149:     public string newPost(string appKey, string blogid, string username, string password, string content, bool publish)
 150:     {
 151:         throw new NotImplementedException();
 152:     }
 153:  
 154:     public bool setTemplate(string appKey, string blogid, string username, string password, string template, string templateType)
 155:     {
 156:         throw new NotImplementedException();
 157:     }
 158:  
 159:     #endregion
 160: }

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.



Comments