Posted on March 23, 2008 00:40 by mcollins

I created a screencast showing Microsoft Office Word 2007 talking to my implementation of the MetaWeblog API.  The video is 23 minutes in length, but because Silverlight Streaming Services has a 10 minute video limit, I had to break the video up into three parts.

Word to MetaWeblog API Part 1_Thumb

Check them out:



Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted on March 22, 2008 15:58 by mcollins

In the previous post of this series, I introduced you to the Blogger API.  The Blogger API is the first of a series of APIs based on the XML-RPC protocol that are used to publish content from WYSIWYG applications such as Windows Live Writer or Microsoft Office Word 2007 to websites such as blogs, knowledge bases, or FAQ-type applications.  In this post, we're going to go to the next step and discuss the MetaWeblog API which refined and enhanced the Blogger API.

There are a couple of specifications available for the MetaWeblog API:

  • The "official" specification is here.
  • The Microsoft implementation of the MetaWeblog API for the Live Spaces service is here.

 

The MetaWeblog API doesn't completely replace the Blogger API.  Instead, it enhances the API and provides better support for publishing richer content to websites.  For example, in the Blogger API, we didn't have the concept of categories for organizing our content and making it easier to find.  With MetaWeblog, we have the concept of categories.  Another addition was that Blogger's blogger.newPost operation only posted the content of a blog post to the blog.  The blog post didn't have a title for indexing or summarizing the content of a page, for example in a table of contents.  MetaWeblog adds an explicit title to the post definition.

The MetaWeblog API defines the following operations on top of the Blogging API:

  • metaWeblog.editPost: edits a post that has been published to the blog or website
  • metaWeblog.getCategories: gets a list of categories that exist on the blog or website
  • metaWeblog.getPost: gets the content for a post that has been published to the blog or website
  • metaWeblog.getRecentPosts: gets the most recent posts from the blog or website
  • metaWeblog.newMediaObject: uploads the contents of a media object such as an image to the blog or website
  • metaWeblog.newPost: publishes a post to the blog or website

 

You'll notice that both the Blogger API and the MetaWeblog API define editPost and newPost operations.  The MetaWeblog API operations obsolete the Blogger API's operations, so with MetaWeblog we shouldn't have to worry about implementing those operations.  You still can implement them if you have compatibility concerns, but most MetaWeblog clients won't use the Blogger API operations.

The two big additions to the MetaWeblog API are the getCategories and newMediaObject operations.  As we discussed, getCategories allows blogs or websites to implement the concept of categories in order to organize content on a blog or website.  Additionally, newMediaObject allows WYSIWYG clients such as Microsoft Office Word or Windows Live Writer publish media objects such as images that are embedded inside the content of a post.  This is an exciting edition to the publishing process because content authors don't need to separately publish images to the website using FTP or some other method.  Instead, the WYSIWYG client can publish both text and images at the same time.

The other big difference is that the MetaWeblog API introduces the formal concept of a post object.  While a post in the Blogger API consisted of just the text for the post content, in the MetaWeblog API, the post has now become a structure based on the RSS specification for uploading the post content with additional metadata such as the title and categories for the post.  In the MetaWeblog API, the following fields are defined for a post:

  • author: The name or email address for the author of a post. This field corresponds to the <author> element in an RSS item.  This field normally isn't used when publishing a new post to a website, but may be included in the post data when calling metaWeblog.getPost to get the data for an existing post.
  • categories: An array of strings containing the categories that the post is associated with.
  • comments: The URL of the comments page for the post.  This field is normally present when calling metaWeblog.getPost to get the data for an existing post.
  • dateCreated: The date and time when the post was created.  Usually present when getting a post using metaWeblog.getPost.
  • description: The HTML text for the post.
  • enclosure: A structure describing a file that has been attached to the post.
  • excerpt: A short summary of a post, like an abstract.
  • keywords: Used to attach keywords to a post.  Some blogging engines use this field to create tags to mark related posts.
  • link: The URL for the blog post.  Appears when using metaWeblog.getPost.
  • publicationDate: The date and time that the post should be published.
  • publish: True or false to indicate if the post has been published.
  • source: The source of the post if the post originated from another site or RSS feed.
  • title: The title of the blog post.

 

The metaWeblog.newPost operation is defined below:

   1: [XmlRpcMethod("metaWeblog.newPost",
   2:     Description = "Publishes a new post to the blog.")]
   3: string NewPost(string blogId, string userName, string password,
   4:     XmlRpcStruct post, bool publish);

 

Here, I use the XML-RPC.NET's implementation of XmlRpcStruct to represent the actual post content.  I could have created a class with fields matching the fields of a post, but I actively chose to use XmlRpcStruct instead.  The reasoning is mostly for future expansion or to support custom extensions to the post data structure.  By accessing the post information using a XmlRpcStruct instead of a class, my implementation of NewPost can access the custom information without my having to expand or change a post structure in the future to support enhancements.  All of the other parameters of NewPost are the same as in the Blogger API, and the return value is still the unique identifier of the new post.

The metaWeblog.editPost operation also is updated to accept a post as a data structure:

   1: [XmlRpcMethod("metaWeblog.editPost",
   2:     Description = "Updates a post that has been posted to the blog.")]
   3: bool EditPost(string postId, string userName, string password,
   4:     Post post, bool publish);

 

The rest of the behavior of EditPost mirrors the Blogger API's version.

You'll notice that the MetaWeblog API doesn't have a delete operation.  The MetaWeblog API is implemented alongside the Blogger API, so it's going to be expected that clients can continue to use the blogger.deletePost operation to delete posts from a website.

The metaWeblog.getCategories operation has been defined to allow WYSIWYG editors to query the blog to determine what categories are defined for the blog:

   1: [XmlRpcMethod("metaWeblog.getCategories",
   2:     Description = "Returns the categories for the blog.")]
   3: Category[] GetCategories(string blogId, string userName,
   4:     string password);

 

The Category data structure that is returned by this operation is defined below:

   1: [Serializable]
   2: [XmlRpcMissingMapping(MappingAction.Error)]
   3: public class Category {
   4:     /// <summary>
   5:     /// The title of the category.
   6:     /// </summary>
   7:     [SuppressMessage("Microsoft.Design", "CA1051",
   8:         Justification = "Public fields must be exposed for XML-RPC.NET")]
   9:     public string description;
  10:     
  11:     /// <summary>
  12:     /// The URL of the web page where the category's contents can be
  13:     /// viewed.
  14:     /// </summary>
  15:     [XmlRpcMissingMapping(MappingAction.Ignore)]
  16:     [SuppressMessage("Microsoft.Design", "CA1051",
  17:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  18:     public string htmlUrl;
  19:  
  20:     /// <summary>
  21:     /// The URL of the RSS feed for the category.
  22:     /// </summary>
  23:     [XmlRpcMissingMapping(MappingAction.Ignore)]
  24:     [SuppressMessage("Microsoft.Design", "CA1051",
  25:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  26:     [SuppressMessage("Microsoft.Naming", "CA1704",
  27:         Justification = "The field name is specified by the MetaWeblog " +
  28:         "API")]
  29:     public string rssUrl;
  30:  
  31:     /// <summary>
  32:     /// The title of the category.
  33:     /// </summary>
  34:     [SuppressMessage("Microsoft.Design", "CA1051",
  35:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  36:     public string title;
  37: }

 

Windows Live Writer expects that the title and description fields of this structure will have the same value.  It's probably a good idea to just make that the default functionality for your implementation.

The metaWeblog.getPost operation is used to retrieve a post from the website using the post's identifier:

   1: [XmlRpcMethod("metaWeblog.getPost",
   2:     Description = "Retrieves the content for a post from the blog.")]
   3: Post GetPost(string postId, string userName, string password);

 

The GetPost operation will return a data structure containing a post.  You'll notice that my definition of this method is returning a Post object rather than an XmlRpcStruct.  To make it easier to implement this method, I subclassed the XmlRpcStruct class to define a Post class with properties that I could set.  Here's the code:

   1: /// <summary>
   2: /// Represents a post that is sent or received by a MetaWeblog service.
   3: /// </summary>
   4: [Serializable]
   5: [SuppressMessage("Microsoft.Design", "CA1035",
   6:     Justification = "Supports XML-RPC.NET")]
   7: [SuppressMessage("Microsoft.Naming", "CA1710",
   8:     Justification = "Supports XML-RPC.NET")]
   9: [SuppressMessage("Microsoft.Usage", "CA2229",
  10:     Justification = "Supports XML-RPC.NET")]
  11: public class Post : XmlRpcStruct {
  12:     /// <summary>
  13:     /// The name of the <see cref="AllowComments"/> field.
  14:     /// </summary>
  15:     public const string AllowCommentsName = "mt_allow_comments";
  16:  
  17:     /// <summary>
  18:     /// The name of the <see cref="AllowPings"/> field.
  19:     /// </summary>
  20:     public const string AllowPingsName = "mt_allow_pings";
  21:  
  22:     /// <summary>
  23:     /// The name of the <see cref="Author"/> field.
  24:     /// </summary>
  25:     public const string AuthorName = "author";
  26:  
  27:     /// <summary>
  28:     /// The name of the <see cref="BaseName"/> field.
  29:     /// </summary>
  30:     public const string BaseNameName = "mt_base_name";
  31:  
  32:     /// <summary>
  33:     /// The name of the <see cref="Body"/> field.
  34:     /// </summary>
  35:     public const string BodyName = "mt_text_more";
  36:  
  37:     /// <summary>
  38:     /// The name of the <see cref="Categories"/> field.
  39:     /// </summary>
  40:     public const string CategoriesName = "categories";
  41:  
  42:     /// <summary>
  43:     /// The name of the <see cref="Comments"/> field.
  44:     /// </summary>
  45:     public const string CommentsName = "comments";
  46:  
  47:     /// <summary>
  48:     /// The name of the <see cref="DateCreated"/> field.
  49:     /// </summary>
  50:     public const string DateCreatedName = "dateCreated";
  51:  
  52:     /// <summary>
  53:     /// The name of the <see cref="Description"/> field.
  54:     /// </summary>
  55:     public const string DescriptionName = "description";
  56:  
  57:     /// <summary>
  58:     /// The name of the <see cref="Enclosure"/> field.
  59:     /// </summary>
  60:     public const string EnclosureName = "enclosure";
  61:  
  62:     /// <summary>
  63:     /// The name of the <see cref="Excerpt"/> field.
  64:     /// </summary>
  65:     public const string ExcerptName = "excerpt";
  66:  
  67:     /// <summary>
  68:     /// The name of the <see cref="Guid"/> field.
  69:     /// </summary>
  70:     public const string GuidName = "guid";
  71:  
  72:     /// <summary>
  73:     /// The name of the <see cref="Keywords"/> field.
  74:     /// </summary>
  75:     public const string KeywordsName = "keywords";
  76:  
  77:     /// <summary>
  78:     /// The name of the <see cref="Link"/> field.
  79:     /// </summary>
  80:     public const string LinkName = "link";
  81:  
  82:     /// <summary>
  83:     /// The name of the <see cref="Password"/> field.
  84:     /// </summary>
  85:     public const string PasswordName = "password";
  86:  
  87:     /// <summary>
  88:     /// The name of the <see cref="PublicationDate"/> field.
  89:     /// </summary>
  90:     public const string PublicationDateName = "publicationDate";
  91:  
  92:     /// <summary>
  93:     /// The name of the <see cref="Publish"/> field.
  94:     /// </summary>
  95:     public const string PublishName = "publish";
  96:  
  97:     /// <summary>
  98:     /// The name of the <see cref="Slug"/> field.
  99:     /// </summary>
 100:     public const string SlugName = "wp_slug";
 101:  
 102:     /// <summary>
 103:     /// The name of the <see cref="Source"/> field.
 104:     /// </summary>
 105:     public const string SourceName = "source";
 106:  
 107:     /// <summary>
 108:     /// The name of the <see cref="Title"/> field.
 109:     /// </summary>
 110:     public const string TitleName = "title";
 111:  
 112:     /// <summary>
 113:     /// The name of the <see cref="TrackbackPingUrls"/> field.
 114:     /// </summary>
 115:     public const string TrackbackPingUrlsName = "mt_tb_ping_urls";
 116:  
 117:     /// <summary>
 118:     /// The name of the <see cref="WPAuthor"/> field.
 119:     /// </summary>
 120:     public const string WPAuthorName = "wp_author";
 121:  
 122:     /// <summary>
 123:     /// Gets or sets whether comments are allowed for the post.
 124:     /// </summary>
 125:     /// <remarks>
 126:     /// <para>
 127:     /// This field corresponds to the <c>mt_allow_comments</c> field
 128:     /// of the post structure.
 129:     /// </para>
 130:     /// <para>