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>
 131:     /// <list>
 132:     /// <listheader>
 133:     /// <term>Value</term>
 134:     /// <description>Description</description>
 135:     /// </listheader>
 136:     /// <item>
 137:     /// <term>0</term>
 138:     /// <description>No comments are allowed.</description>
 139:     /// </item>
 140:     /// <item>
 141:     /// <term>1</term>
 142:     /// <description>Comments can be read and written.</description>
 143:     /// </item>
 144:     /// <item>
 145:     /// <term>2</term>
 146:     /// <description>Comments can be read but not written.</description>
 147:     /// </item>
 148:     /// </list>
 149:     /// </para>
 150:     /// </remarks>
 151:     public int AllowComments {
 152:         get { return (int)this[AllowCommentsName]; }
 153:         set { this[AllowCommentsName] = value; }
 154:     }
 155:  
 156:     /// <summary>
 157:     /// Gets or sets whether Trackback pings are allowed on the post.
 158:     /// </summary>
 159:     public int AllowPings {
 160:         get { return (int)this[AllowPingsName]; }
 161:         set { this[AllowPingsName] = value; }
 162:     }
 163:  
 164:     /// <summary>
 165:     /// Gets or sets the email address of the author of the blog post.
 166:     /// </summary>
 167:     public string Author {
 168:         get { return (string)this[AuthorName]; }
 169:         set { this[AuthorName] = value; }
 170:     }
 171:  
 172:     /// <summary>
 173:     /// Gets or sets the base name for the post.
 174:     /// </summary>
 175:     /// <remarks>
 176:     /// This property corresponds to the <c>mt_basename</c> field of the
 177:     /// post structure.
 178:     /// </remarks>
 179:     public string BaseName {
 180:         get { return (string)this[BaseNameName]; }
 181:         set { this[BaseNameName] = value; }
 182:     }
 183:  
 184:     /// <summary>
 185:     /// Gets or sets the body text of the blog post.
 186:     /// </summary>
 187:     /// <remarks>
 188:     /// This field corresponds to the <c>mt_text_more</c> field of the 
 189:     /// post structure.
 190:     /// </remarks>
 191:     public string Body {
 192:         get { return (string)this[BodyName]; }
 193:         set { this[BodyName] = value; }
 194:     }
 195:  
 196:     /// <summary>
 197:     /// Gets or sets the categories for the blog post.
 198:     /// </summary>
 199:     [SuppressMessage("Microsoft.Performance", "CA1819",
 200:         Justification = "Supports XML-RPC.NET")]
 201:     public string[] Categories {
 202:         get { return (string[])this[CategoriesName]; }
 203:         set { this[CategoriesName] = value; }
 204:     }
 205:  
 206:     /// <summary>
 207:     /// Gets or sets the URL of the comments page for the post.
 208:     /// </summary>
 209:     public string Comments {
 210:         get { return (string)this[CommentsName]; }
 211:         set { this[CommentsName] = value; }
 212:     }
 213:  
 214:     /// <summary>
 215:     /// Gets or sets the date and time that a post was created.
 216:     /// </summary>
 217:     public DateTime DateCreated {
 218:         get { return (DateTime)this[DateCreatedName]; }
 219:         set { this[DateCreatedName] = value; }
 220:     }
 221:  
 222:     /// <summary>
 223:     /// Gets or sets the content or body of the post.
 224:     /// </summary>
 225:     public string Description {
 226:         get { return (string)this[DescriptionName]; }
 227:         set { this[DescriptionName] = value; }
 228:     }
 229:  
 230:     /// <summary>
 231:     /// Gets or sets a structure containing information about a media
 232:     /// object that is attached to the item.
 233:     /// </summary>
 234:     [SuppressMessage("Microsoft.Usage", "CA2227",
 235:         Justification = "Supports XML-RPC.NET")]
 236:     public XmlRpcStruct Enclosure {
 237:         get { return (XmlRpcStruct)this[EnclosureName]; }
 238:         set { this[EnclosureName] = value; }
 239:     }
 240:  
 241:     /// <summary>
 242:     /// Gets or sets the excerpt for the post.
 243:     /// </summary>
 244:     /// <remarks>
 245:     /// This property corresponds to the <c>mt_excerpt</c> field of the
 246:     /// post structure.
 247:     /// </remarks>
 248:     public string Excerpt {
 249:         get { return (string)this[ExcerptName]; }
 250:         set { this[ExcerptName] = value; }
 251:     }
 252:  
 253:     /// <summary>
 254:     /// Gets or sets the unique identifier for the post.
 255:     /// </summary>
 256:     public string Guid {
 257:         get { return (string)this[GuidName]; }
 258:         set { this[GuidName] = value; }
 259:     }
 260:  
 261:     /// <summary>
 262:     /// Gets or sets the keywords for the post.
 263:     /// </summary>
 264:     /// <remarks>
 265:     /// This field corresponds to the <c>mt_keywords</c> field of the
 266:     /// post structure.
 267:     /// </remarks>
 268:     public string Keywords {
 269:         get { return (string)this[KeywordsName]; }
 270:         set { this[KeywordsName] = value; }
 271:     }
 272:  
 273:     /// <summary>
 274:     /// Gets or sets the URL of the item.
 275:     /// </summary>
 276:     public string Link {
 277:         get { return (string)this[LinkName]; }
 278:         set { this[LinkName] = value; }
 279:     }
 280:  
 281:     /// <summary>
 282:     /// Gets or sets the password for the post.
 283:     /// </summary>
 284:     /// <remarks>
 285:     /// This field corresponds to the <c>wp_password</c> field of the
 286:     /// post structure.
 287:     /// </remarks>
 288:     public string Password {
 289:         get { return (string)this[PasswordName]; }
 290:         set { this[PasswordName] = value; }
 291:     }
 292:  
 293:     /// <summary>
 294:     /// Gets or sets the publication date for the post.
 295:     /// </summary>
 296:     public DateTime PublicationDate {
 297:         get { return (DateTime)this[PublicationDateName]; }
 298:         set { this[PublicationDateName] = value; }
 299:     }
 300:  
 301:     /// <summary>
 302:     /// Gets or sets a flag indicating whether the post is published.
 303:     /// </summary>
 304:     public bool Publish {
 305:         get { return (bool)this[PublishName]; }
 306:         set { this[PublishName] = value; }
 307:     }
 308:  
 309:     /// <summary>
 310:     /// Gets or sets the slug value.
 311:     /// </summary>
 312:     /// <remarks>
 313:     /// This property corresponds to the <c>wp_slug</c> field of the
 314:     /// post structure.
 315:     /// </remarks>
 316:     public string Slug {
 317:         get { return (string)this[SlugName]; }
 318:         set { this[SlugName] = value; }
 319:     }
 320:  
 321:     /// <summary>
 322:     /// Gets or sets the source information for the post.
 323:     /// </summary>
 324:     [SuppressMessage("Microsoft.Usage", "CA2227",
 325:         Justification = "Supports XML-RPC.NET")]
 326:     public XmlRpcStruct Source {
 327:         get { return (XmlRpcStruct)this[SourceName]; }
 328:         set { this[SourceName] = value; }
 329:     }
 330:  
 331:     /// <summary>
 332:     /// Gets or sets the title of the blog post.
 333:     /// </summary>
 334:     public string Title {
 335:         get { return (string)this[TitleName]; }
 336:         set { this[TitleName] = value; }
 337:     }
 338:  
 339:     /// <summary>
 340:     /// Gets or sets the collection of Trackback ping URLs associated
 341:     /// with the post.
 342:     /// </summary>
 343:     /// <remarks>
 344:     /// This property corresponds to the <c>mt_tb_ping_urls</c> field
 345:     /// of the blog post.
 346:     /// </remarks>
 347:     [SuppressMessage("Microsoft.Performance", "CA1819",
 348:         Justification = "Supports XML-RPC.NET")]
 349:     public string[] TrackbackPingUrls {
 350:         get { return (string[])this[TrackbackPingUrls]; }
 351:         set { this[TrackbackPingUrls] = value; }
 352:     }
 353:  
 354:     /// <summary>
 355:     /// Gets or sets the author of the post.
 356:     /// </summary>
 357:     /// <remarks>
 358:     /// This field corresponds to the <c>wp_author</c> field of the
 359:     /// post structure.
 360:     /// </remarks>
 361:     public string WPAuthor {
 362:         get { return (string)this[WPAuthor]; }
 363:         set { this[WPAuthor] = value; }
 364:     }
 365: }

 

The first part of the Post class's implementation contains constant definitions with the names of the fields as they appear in the structure.  The second part contains properties that make it easier to set the values of these fields.  I also made the constants for the field names public so that they could be used for the implementation of EditPost and NewPost.  You'll also notice that my Post definition has additional fields of the post that I didn't discuss earlier.  These are extension fields that are defined by the Moveable Type and WordPress APIs, which we'll discuss in the future posts as we discuss each of those extensions to our web publishing service.

The metaWeblog.getRecentPosts operation is useful to retrieve the most recently publish posts on the blog or website.  This could be useful in the implementation of a WYSIWYG editor.

   1: [XmlRpcMethod("metaWeblog.getRecentPosts",
   2:     Description = "Retrieves the most recent posts from the blog.")]
   3: XmlRpcStruct[] GetRecentPosts(string blogId, string userName,
   4:     string password, int numberOfPosts);

 

You can limit the number of posts that are returned by using the numberOfPosts parameter on the GetRecentPosts operation.

Finally, MetaWeblog defined the metaWeblog.NewMediaObject method to support uploading objects such as images embedded in a blog post to the website.

   1: [XmlRpcMethod("metaWeblog.newMediaObject",
   2:     Description = "Publishes a media object on the blog.")]
   3: NewMediaObjectResponse NewMediaObject(string blogId, string userName,
   4:     string password, MediaObject mediaObject);

 

The MediaObject class used as a parameter for the NewMediaObject operation looks like this:

   1: /// <summary>
   2: /// Stores information about a media object being uploaded to a blog
   3: /// using the <see cref="IMetaWeblogService.NewMediaObject"/> operation.
   4: /// </summary>
   5: [Serializable]
   6: [XmlRpcMissingMapping(MappingAction.Error)]
   7: public class MediaObject {
   8:     /// <summary>
   9:     /// The binary contents of the media object.
  10:     /// </summary>
  11:     [SuppressMessage("Microsoft.Design", "CA1051",
  12:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  13:     public byte[] bits;
  14:  
  15:     /// <summary>
  16:     /// The name of the media object.
  17:     /// </summary>
  18:     [SuppressMessage("Microsoft.Design", "CA1051",
  19:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  20:     public string name;
  21:  
  22:     /// <summary>
  23:     /// The media object's MIME content type.
  24:     /// </summary>
  25:     [SuppressMessage("Microsoft.Design", "CA1051",
  26:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  27:     public string type;
  28: }

 

In this structure, name is the name or file name of the media object being uploaded, type is the MIME content type for the data being uploaded, and bits is a byte array that contains the full media object data.

The return value for the metaWeblog.newMediaObject operation is the URL of the new media object, wrapped in a structure:

   1: /// <summary>
   2: /// Represents the response of the
   3: /// <see cref="IMetaWeblogService.NewMediaObject"/> operation.
   4: /// </summary>
   5: [Serializable]
   6: [XmlRpcMissingMapping(MappingAction.Error)]
   7: public class NewMediaObjectResponse {
   8:     /// <summary>
   9:     /// The URL where the media object can be downloaded from.
  10:     /// </summary>
  11:     [SuppressMessage("Microsoft.Design", "CA1051",
  12:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  13:     public string url;
  14: }

 

The full source code for the MetaWeblog API interface is below:

   1: /// <summary>
   2: /// Defines the operations supported by the MetaWeblog API for publishing
   3: /// content to a website or blog.
   4: /// </summary>
   5: public interface IMetaWeblogService {
   6:     /// <summary>
   7:     /// Updates a post that has been posted on the blog.
   8:     /// </summary>
   9:     /// <param name="postId">
  10:     /// The unique identifier for the post to update.
  11:     /// </param>
  12:     /// <param name="userName">
  13:     /// The login for a user that can edit the blog post.
  14:     /// </param>
  15:     /// <param name="password">The user's password.</param>
  16:     /// <param name="post">
  17:     /// The updated contents of the post.
  18:     /// </param>
  19:     /// <param name="publish">
  20:     /// True if the post should be published.
  21:     /// </param>
  22:     /// <returns>
  23:     /// Returns true if the post was successfully updated.
  24:     /// </returns>
  25:     [XmlRpcMethod("metaWeblog.editPost",
  26:         Description = "Updates a post that has been posted to the blog.")]
  27:     bool EditPost(string postId, string userName, string password,
  28:         Post post, bool publish);
  29:  
  30:     /// <summary>
  31:     /// Returns the list of categories for the blog.
  32:     /// </summary>
  33:     /// <param name="blogId">
  34:     /// The unique identifier of the blog.
  35:     /// </param>
  36:     /// <param name="userName">
  37:     /// The login for a user with administrator rights to the blog.
  38:     /// </param>
  39:     /// <param name="password">The user's password.</param>
  40:     /// <returns>
  41:     /// Returns an array containing the categories for the blog.
  42:     /// </returns>
  43:     [XmlRpcMethod("metaWeblog.getCategories",
  44:         Description = "Returns the categories for the blog.")]
  45:     Category[] GetCategories(string blogId, string userName,
  46:         string password);
  47:  
  48:     /// <summary>
  49:     /// Retrieves the contents of a post from the blog.
  50:     /// </summary>
  51:     /// <param name="postId">
  52:     /// The unique identifier of the post to retrieve.
  53:     /// </param>
  54:     /// <param name="userName">
  55:     /// The login for a user that can access the post.
  56:     /// </param>
  57:     /// <param name="password">
  58:     /// The user's password.
  59:     /// </param>
  60:     /// <returns>
  61:     /// Returns the contents of the post.
  62:     /// </returns>
  63:     [XmlRpcMethod("metaWeblog.getPost",
  64:         Description = "Retrieves the content for a post from the blog.")]
  65:     Post GetPost(string postId, string userName, string password);
  66:  
  67:     /// <summary>
  68:     /// Retrieves the most recent posts from the blog.
  69:     /// </summary>
  70:     /// <param name="blogId">
  71:     /// The unique identifier of the blog.
  72:     /// </param>
  73:     /// <param name="userName">
  74:     /// The login for a user that can access the blog.
  75:     /// </param>
  76:     /// <param name="password">The user's password.</param>
  77:     /// <param name="numberOfPosts">
  78:     /// The maximum number of posts to retrieve.
  79:     /// </param>
  80:     /// <returns>
  81:     /// Returns an array of blog posts.
  82:     /// </returns>
  83:     [XmlRpcMethod("metaWeblog.getRecentPosts",
  84:         Description = "Retrieves the most recent posts from the blog.")]
  85:     XmlRpcStruct[] GetRecentPosts(string blogId, string userName,
  86:         string password, int numberOfPosts);
  87:  
  88:     /// <summary>
  89:     /// Publishes a media object to the website or blog.
  90:     /// </summary>
  91:     /// <remarks>
  92:     /// This operation is used to support publishing images or other
  93:     /// items that may be embedded in a blog post.
  94:     /// </remarks>
  95:     /// <param name="blogId">
  96:     /// The unique identifier of the blog.
  97:     /// </param>
  98:     /// <param name="userName">
  99:     /// The login for a user that can publish to the blog.
 100:     /// </param>
 101:     /// <param name="password">The user's password.</param>
 102:     /// <param name="mediaObject">
 103:     /// The contents of the media object to publish to the blog.
 104:     /// </param>
 105:     /// <returns>
 106:     /// Returns the URL where the media object can be downloaded from.
 107:     /// </returns>
 108:     [XmlRpcMethod("metaWeblog.newMediaObject",
 109:         Description = "Publishes a media object on the blog.")]
 110:     NewMediaObjectResponse NewMediaObject(string blogId, string userName,
 111:         string password, MediaObject mediaObject);
 112:  
 113:     /// <summary>
 114:     /// Publishes a new post to the website or blog.
 115:     /// </summary>
 116:     /// <param name="blogId">
 117:     /// The unique identifier of the blog.
 118:     /// </param>
 119:     /// <param name="userName">
 120:     /// The login for a user that can publish to the blog.
 121:     /// </param>
 122:     /// <param name="password">The user's password.</param>
 123:     /// <param name="post">The contents of the post.</param>
 124:     /// <param name="publish">
 125:     /// True if the post should be published.
 126:     /// </param>
 127:     /// <returns>
 128:     /// Returns the unique identifier of the new post.
 129:     /// </returns>
 130:     [XmlRpcMethod("metaWeblog.newPost",
 131:         Description = "Publishes a new post to the blog.")]
 132:     string NewPost(string blogId, string userName, string password,
 133:         XmlRpcStruct post, bool publish);
 134: }

 

Now that we have the MetaWeblog API defined in a format that we can implement in .NET, the next step is to start implementing it and looking at how Microsoft Office Word 2007 and Windows Live Writer will attempt to use the API to support their editing functions.  We will cover that in the next post, hopefully with a little video demonstration.

Technorati tags: , , , ,


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 12:24 by mcollins

On occasion, I have needed to write web applications for personal use or for my employers or clients.  These web applications have done different things, from being a full-fledged ERP application to being a personal website (my arch-nemesis) to being a website for an actual company.  One constant function that always seems to pop up for websites and web applications is the concept of content management.

(I should probably qualify this post by saying that I define a website as being what we normally expect a site to be.  For example, this blog is a website.  MSNBC News is a website.  However, there's an administrative piece to this blog, which I would consider a web application.  An ERP application with a web front end is a web application.)

In discussing content management, the first question to pop up is based around keeping content up to date.  "How do we update existing content?"  "How do we publish new items to the site?" "Can our marketing department do this?"

The good news to all of these topics is yes, but it requires some work from the developers to implement.  We live in a golden age right now in that the web has finally matured, and some of the desktop tools are starting to catch up.  In the earlier days of the web, users had to type the new content into a web form.  WYSIWYG editors based on MSHTML for example started showing up to allow users to do rich text formatting inside of a web page.  That was a step forward, but it really wasn't what everyone wanted to get to.

Now in the present, we have two really good tools for content management and publishing: Windows Live Writer from Microsoft and Microsoft Office Word 2007.  Both tools offer an ideal solution (and Windows Live Writer offers a better solution).  Both tools let content writers create, proof, and edit their content on their desktop or laptop machines, and when the content is ready to be published, both tools can communicate with web applications that implement open standards to publish the content to the websites.

So, how does this content publishing get done?  The answer is in the standards that other applications are using.  We could always build our own interface, but the advantages are that we don't need to rebuild Windows Live Writer if we follow the other open standards.  The most popular (in my opinion) standards right now are based on an XML-RPC protocol called the MetaWeblog API.  Using MetaWeblog, a tool such as Word or Windows Live Writer can make HTTP calls to the website and transfer the text for content plus any associated images.  MetaWeblog is a standard that developed from another earlier API that was developed for the Blogger blogging service.  MetaWeblog fixed some of the shortcomings of the API, but rather than replace the Blogger API, MetaWeblog enhanced it while leaving some of the functionality of the Blogger API in use.  In my next post, we'll take a look at the MetaWeblog API.  In this post however, we're going to look at the Blogger API.

First, my implementation of the Blogger API is using XML-RPC.NET from Cook Computing.  This is an excellent .NET implementation of the XML-RPC protocol and integrates very nicely into any ASP.NET website or web application.

The Blogger API specification can be found here.

The Blogger API defines seven operations that can be used by clients:

  • blogger.deletePost: deletes an existing post from a blog
  • blogger.editPost: edits an existing post on a blog
  • blogger.getTemplate: retrieves the text for the blog template for use in WYSIWYG editors
  • blogger.getUserInfo: retrieves the name of a user and the user's email address from the blog
  • blogger.getUsersBlogs: retrieves the list of blogs on the website that the user can post to
  • blogger.newPost: publishes a new post to a blog
  • blogger.setTemplate: uploads a new HTML template for a blog

 

Now, reading through this list of operations, two words are going to stick out to most readers: "post" and "blog."  You may now be asking yourself, is this really relevant to me because I'm not writing a blog.  The answer is yes, if you think of this API and these terms in abstract terms (couldn't come up with a better word than repeating terms...it's a Saturday morning...you can't expect too much from me).

So if I'm not writing a blog, can I still use the Blogger API?  Sure.  Think about it from the concept of another application.  What if you're building a knowledge base or a FAQ list for a customer.  They're not blogs, but they store content like a blog.  And a post could be a question on an FAQ, or a knowledge base item on a knowledge base, couldn't it?  Or if you're writing a news website, couldn't the "blog" be the news website and a post be an article?  The answer to al of these questions is yes.  You just need to fit these terms into the content management solution that you're building.

Back to our Blogger API implementation. the first thing that we want to do with our new website is to publish content to the site.  So we're going to start out with defining the blogger.newPost operation to allow us to publish contet to a website using the Blogger API.  Here's how that method is defined:

   1: [XmlRpcMethod("blogger.newPost",
   2:     Description = "Makes a new post to a blog.")]
   3: string NewPost(string appKey, string blogId, string userName,
   4:     string password, string content, bool publish);

 

In the above code sample, I defined a method named NewPost which will implement the blogger.newPost operation.  You'll also see on line 1, that I decorated the NewPost method with the XmlRpcMethodAttribute attribute.  The XmlRpcMethodAttribute class is defined by XML-RPC.NET and allows me to bind an XML-RPC operation to a .NET method.  When we actually add this method to an XML-RPC web service later in this post, any XML-RPC requests received by our web application requesting the method blogger.newPost will be directed to our NewPost implementation.

The NewPost method takes six parameters.  The appKey parameter is for the most part ignored.  I think that back in the early days of Blogger, Blogger probably assigned unique keys to each client application that had signed up to use the XML-RPC API.  Over time, I'm guessing that they moved away from this practice, which is why this key is not used.  It's very safe to go ahead and ignore this parameter.

The blogId parameter is the unique identifier of the blog where the new content should be posted to.  Again, if you're not writing a blog, this could be a unique identifier you assign to your website, a category, some sub-application, etc.  The meaning of blogId is open for you to determine in your implementation.

The userName and password parameters are obviously the user name and password of the user publishing the content to the website.  You could use these fields in an ASP.NET application for example to verify the user against the ASP.NET membership database or your proprietary user store.

The content parameter is the important stuff.  This parameter will contain the text or HTML text of the content being published to the website.  This is obviously the data that you're going to store and display on your web pages.

The publish parameter is a boolean parameter that is set to true if the author wants to make the content immediately available on the website, or save the content to be published at a later time if this parameter is set to false.  When I implement this interface, I usually have a binary field on my content table that I store this value in.  I'll then use that binary field to determine what content gets displayed on my web page.

The return value for the blogger.NewPost operation is the unique identifier for the new post, if the new post was published or saved to the blog (or website) successfully.

Assuming that we've successfully published a new post up to the website, what happens if we discover an error that we want to fix and update the website with the corrected content?  This is where the blogger.editPost operation comes into play:

   1: [XmlRpcMethod("blogger.editPost",
   2:     Description = "Changes the contents of the specified post.")]
   3: bool EditPost(string appKey, string postId, string userName,
   4:     string password, string content, bool publish);

 

The parameters for the EditPost operation look pretty similar to the NewPost operation, so I won't rehash them.  The only difference is that where NewPost accepted a blogId identifier to identify the blog that the new post was being published to, EditPost accepts the unique identifier of the post being edited.  This is the same value that was returned by NewPost as its return value.  All of the other parameters are the same.

The publish parameter of EditPost, serves the same purpose as in NewPost.  One reason why it's here on the EditPost operation too is that the author may not have published the new content when NewPost was called, but now the author wants to publish the content on the site.  The author can use EditPost to change the value of the "is published" flag.

The return value for EditPost is always true.  If an error occurred in editing or updating the post, then the XML-RPC operation should return a fault, which XML-RPC.NET will turn into a .NET exception.

On to our third operation: blogger.deletePost.  Obviously, this operation is used to delete a post that has already been published on the blog.

   1: [XmlRpcMethod("blogger.deletePost",
   2:     Description = "Deletes a post from the blog.")]
   3: bool DeletePost(string appKey, string postId, string userName,
   4:     string password, bool publish);

 

The only parameter here that doesn't make sense if the publish parameter.  It can be safely ignored.  Again, this operation will always return true, or will return a fault if an error occurs.

The next operation of interest is blogger.getUsersBlogs.  This operation is used in a multi-blog scenario where an author has the capability of publishing to many blogs or content areas.  Recall that the blogger.newPost operation requires the unique identifier of a blog to publish to?  Where does a client application get a list of the available blogs?  blogger.getUsersBlogs.

   1: [XmlRpcMethod("blogger.getUsersBlogs",
   2:     Description = "Returns information about all of the blogs that " +
   3:     "the user is a member of.")]
   4: Blog[] GetUsersBlogs(string appKey, string userName, string password);

 

The return value of blogger.getUsersBlogs is an array of structures containing information about the available blogs.  This structure looks like the following:

   1: [Serializable]
   2: [XmlRpcMissingMapping(MappingAction.Error)]
   3: public class Blog {
   4:     /// <summary>
   5:     /// The unique identifier of the blog.
   6:     /// </summary>
   7:     [SuppressMessage("Microsoft.Design", "CA1051",
   8:         Justification = "Public fields must be exposed for XML-RPC.NET")]
   9:     [SuppressMessage("Microsoft.Naming", "CA1704",
  10:         Justification = "The field name is specified by the Blogger API")]
  11:     public string blogid;
  12:  
  13:     /// <summary>
  14:     /// The name of the blog.
  15:     /// </summary>
  16:     [SuppressMessage("Microsoft.Design", "CA1051",
  17:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  18:     public string blogName;
  19:  
  20:     /// <summary>
  21:     /// The URL where the blog is hosted.
  22:     /// </summary>
  23:     [SuppressMessage("Microsoft.Design", "CA1051",
  24:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  25:     public string url;
  26: }

 

This structure has three fields: blogid, blogName, and url.  The blogid field is the blog's unique identifier that is sent to blogger.newPost.  The blogName parameter is something descriptive such as the title of the blog.  The url parameter is the URL of the home page for the blog.

A couple of .NET implementation notes, the structure doesn't have to be serializable, but I decorated the type with the SerializableAttribute attribute for my own use internally to my application.  Also, I added the SuppressMessageAttribute attributes to the structure to get around the FxCop errors that were being raised when validating this code.  At the time that I wrote this, XML-RPC.NET can only read public fields when serializing/deserializing XML-RPC strucutres.  I don't understand why public properties were not acceptable, but this is the way that it works.  I guess that I could always fix XML-RPC.NET, but I'm not losing sleep over this little detail and I want to implement the Blogger API, not rewrite XML-RPC.NET when there's a workaround that I can live with.

Also, I chose a class instead of a structure because internally I might build the list of blogs up in a collection type such as a List.  Reference types such as objects work better with these collection types than value-based types such as structs do.  That was my reasoning behind using a class and not a struct.

The next operation of interest is blogger.getUserInfo.  This operation can be called by a blogging client to get metadata about the blogger.

   1: [XmlRpcMethod("blogger.getUserInfo",
   2:     Description = "Returns information about the specified user.")]
   3: User GetUserInfo(string appKey, string userName, string password);

 

The User structure used by this method is defined below:

   1: [Serializable]
   2: [XmlRpcMissingMapping(MappingAction.Error)]
   3: public class User {
   4:     /// <summary>
   5:     /// The unique identifier for the user.
   6:     /// </summary>
   7:     [SuppressMessage("Microsoft.Design", "CA1051",
   8:         Justification = "Public fields must be exposed for XML-RPC.NET")]
   9:     [SuppressMessage("Microsoft.Naming", "CA1704",
  10:         Justification = "The field name is specified by the Blogger API")]
  11:     public string userid;
  12:  
  13:     /// <summary>
  14:     /// The user's first name.
  15:     /// </summary>
  16:     [SuppressMessage("Microsoft.Design", "CA1051",
  17:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  18:     [SuppressMessage("Microsoft.Naming", "CA1704",
  19:         Justification = "The field name is specified by the Blogger API")]
  20:     public string firstname;
  21:  
  22:     /// <summary>
  23:     /// The user's last name.
  24:     /// </summary>
  25:     [SuppressMessage("Microsoft.Design", "CA1051",
  26:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  27:     [SuppressMessage("Microsoft.Naming", "CA1704",
  28:         Justification = "The field name is specified by the Blogger API")]
  29:     public string lastname;
  30:  
  31:     /// <summary>
  32:     /// The user's nick name.
  33:     /// </summary>
  34:     [SuppressMessage("Microsoft.Design", "CA1051",
  35:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  36:     public string nickname;
  37:  
  38:     /// <summary>
  39:     /// The user's email address.
  40:     /// </summary>
  41:     [SuppressMessage("Microsoft.Design", "CA1051",
  42:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  43:     public string email;
  44:  
  45:     /// <summary>
  46:     /// The URL for the user's website or blog.
  47:     /// </summary>
  48:     [SuppressMessage("Microsoft.Design", "CA1051",
  49:         Justification = "Public fields must be exposed for XML-RPC.NET")]
  50:     public string url;
  51: }

 

Again, I made the same implementation decisions that I did with the above Blog structure.

The final two methods are used by WYSIWYG editors to download or replace the templates being used by the blogs.  As far as I can tell, these operations aren't being used by any current clients, however, here they are for completeness to the specification:

   1: [XmlRpcMethod("blogger.getTemplate",
   2:     Description = "Returns the text of the main or archive index " +
   3:     "template for the specified blog.")]
   4: string GetTemplate(string appKey, string blogId, string userName,
   5:     string password, string templateType);
   6:  
   7: [XmlRpcMethod("blogger.setTemplate",
   8:     Description = "Changes the template for the specified blog.")]
   9: bool SetTemplate(string appKey, string blogId, string userName,
  10:     string password, string templateText, string templateType);

 

The GetTemplate operation returns the text for the specified template.  The templateType parameter is going to identify which template to return.  According to the specification, there may be two templates.  The first template, identified by the type "main," returns the HTML for the home page of the blog.  The second template, identified by the type "archiveIndex," returns the HTML for a page that shows the summary of past posts on the blog.

In the SetTemplate operation, the parameter templateText contains the new HTML content for the home page or archive page, as indicated by the templateType parameter.  Like the other methods above, SetTemplate will always return true unless an error occurs and a fault is returned.

Here's the complete definition of my Blogger API operations, defined in a .NET interface that we'll use in a later post to implement these methods:

   1: /// <summary>
   2: /// Defines the XML-RPC operations that are supported by the Blogger
   3: /// API for clients to use to publish content to a website or blog.
   4: /// </summary>
   5: public interface IBloggerService {
   6:     /// <summary>
   7:     /// Deletes a post from the blog.
   8:     /// </summary>
   9:     /// <param name="appKey">
  10:     /// The unique identifier or passcode of the application deleting
  11:     /// the post. This parameter is typically ignored.
  12:     /// </param>
  13:     /// <param name="postId">
  14:     /// The unique identifier of the post to be deleted.
  15:     /// </param>
  16:     /// <param name="userName">
  17:     /// The login for a user that can delete the post.
  18:     /// </param>
  19:     /// <param name="password">The user's password.</param>
  20:     /// <param name="publish">This parameter is ignored.</param>
  21:     /// <returns>
  22:     /// Returns true if the post was successfully deleted.
  23:     /// </returns>
  24:     [XmlRpcMethod("blogger.deletePost",
  25:         Description = "Deletes a post from the blog.")]
  26:     bool DeletePost(string appKey, string postId, string userName,
  27:         string password, bool publish);
  28:  
  29:     /// <summary>
  30:     /// Changes the contents of the specified post.
  31:     /// </summary>
  32:     /// <remarks>
  33:     /// If <paramref name="publish"/> is set to true, then the post
  34:     /// should be published.
  35:     /// </remarks>
  36:     /// <param name="appKey">
  37:     /// The unique identifier or passcode of the application sending the
  38:     /// post. This parameter is typically ignored.
  39:     /// </param>
  40:     /// <param name="postId">
  41:     /// The unique identifier of the post that will be changed.
  42:     /// </param>
  43:     /// <param name="userName">
  44:     /// The login for a user who has permission to post to the blog.
  45:     /// </param>
  46:     /// <param name="password">
  47:     /// The password for the user.
  48:     /// </param>
  49:     /// <param name="content">
  50:     /// The contents of the post.
  51:     /// </param>
  52:     /// <param name="publish">
  53:     /// True if the post should be published immediately.
  54:     /// </param>
  55:     /// <returns>
  56:     /// Returns true if the post was successfully updated.
  57:     /// </returns>
  58:     [XmlRpcMethod("blogger.editPost",
  59:         Description = "Changes the contents of the specified post.")]
  60:     bool EditPost(string appKey, string postId, string userName,
  61:         string password, string content, bool publish);
  62:  
  63:     /// <summary>
  64:     /// Returns the text of the main or archive index template for the
  65:     /// specified blog.
  66:     /// </summary>
  67:     /// <param name="appKey">
  68:     /// The unique identifier or passcode of the application sending the
  69:     /// post. This parameter is typically ignored.
  70:     /// </param>
  71:     /// <param name="blogId">
  72:     /// The unique identifier of the blog whose template is to be returned.
  73:     /// </param>
  74:     /// <param name="userName">
  75:     /// The login for a user who has administrator permissions on the
  76:     /// specified blog.
  77:     /// </param>
  78:     /// <param name="password">
  79:     /// The user's password.
  80:     /// </param>
  81:     /// <param name="templateType">
  82:     /// <list>
  83:     /// <listheader>
  84:     /// <term>Template type</term>
  85:     /// <description>Description</description>
  86:     /// </listheader>
  87:     /// <item>
  88:     /// <term>main</term>
  89:     /// <description>
  90:     /// Returns the text for the main template of the blog.
  91:     /// </description>
  92:     /// </item>
  93:     /// <item>
  94:     /// <term>archiveIndex</term>
  95:     /// <description>
  96:     /// Returns the text for the archive index template of the blog.
  97:     /// </description>
  98:     /// </item>
  99:     /// </list>
 100:     /// </param>
 101:     /// <returns>
 102:     /// Returns the text of the specified blog template.
 103:     /// </returns>
 104:     [XmlRpcMethod("blogger.getTemplate",
 105:         Description = "Returns the text of the main or archive index " +
 106:         "template for the specified blog.")]
 107:     string GetTemplate(string appKey, string blogId, string userName,
 108:         string password, string templateType);
 109:  
 110:     /// <summary>
 111:     /// Returns information about the requested user.
 112:     /// </summary>
 113:     /// <param name="appKey">
 114:     /// The unique identifier or passcode of the application sending the
 115:     /// post. This parameter is typically ignored.
 116:     /// </param>
 117:     /// <param name="userName">
 118:     /// The login for the user whose information will be retrieved.
 119:     /// </param>
 120:     /// <param name="password">
 121:     /// The password for the user.
 122:     /// </param>
 123:     /// <returns>
 124:     /// Returns a structure containing the user's name, email address,
 125:     /// and website URL.
 126:     /// </returns>
 127:     [XmlRpcMethod("blogger.getUserInfo",
 128:         Description = "Returns information about the specified user.")]
 129:     User GetUserInfo(string appKey, string userName, string password);
 130:  
 131:     /// <summary>
 132:     /// Returns information about all of the blogs that the specified
 133:     /// user is a member of.
 134:     /// </summary>
 135:     /// <param name="appKey">
 136:     /// The unique identifier or passcode of the application sending the
 137:     /// post. This parameter is typically ignored.
 138:     /// </param>
 139:     /// <param name="userName">
 140:     /// The login for the user whose blogs will be retrieved.
 141:     /// </param>
 142:     /// <param name="password">
 143:     /// The password for the user.
 144:     /// </param>
 145:     /// <returns>
 146:     /// Returns an array of <see cref="Blog"/> objects describing the
 147:     /// blogs that the specified user has access to.
 148:     /// </returns>
 149:     [XmlRpcMethod("blogger.getUsersBlogs",
 150:         Description = "Returns information about all of the blogs that " +
 151:         "the user is a member of.")]
 152:     Blog[] GetUsersBlogs(string appKey, string userName, string password);
 153:  
 154:     /// <summary>
 155:     /// Makes a new post to a designated blog.
 156:     /// </summary>
 157:     /// <remarks>
 158:     /// If publish is set to true, then the post should
 159:     /// be published.
 160:     /// </remarks>
 161:     /// <param name="appKey">
 162:     /// The unique identifier or passcode of the application sending the
 163:     /// post. This parameter is typically ignored.
 164:     /// </param>
 165:     /// <param name="blogId">
 166:     /// The unique identifier of the blog that the post will be added to.
 167:     /// </param>
 168:     /// <param name="userName">
 169:     /// The login for a user who has permission to post to the blog.
 170:     /// </param>
 171:     /// <param name="password">
 172:     /// The password for the user.
 173:     /// </param>
 174:     /// <param name="content">
 175:     /// The contents of the post.
 176:     /// </param>
 177:     /// <param name="publish">
 178:     /// True if the post should be published immediately.
 179:     /// </param>
 180:     /// <returns>
 181:     /// Returns the unique identifier of the new post.
 182:     /// </returns>
 183:     [XmlRpcMethod("blogger.newPost",
 184:         Description = "Makes a new post to a blog.")]
 185:     string NewPost(string appKey, string blogId, string userName,
 186:         string password, string content, bool publish);
 187:  
 188:     /// <summary>
 189:     /// Changes the template for the specified blog.
 190:     /// </summary>
 191:     /// <param name="appKey">
 192:     /// The unique identifier or passcode of the application sending the
 193:     /// post.
 194:     /// </param>
 195:     /// <param name="blogId">
 196:     /// The unique identifier of the blog whose template is to be changed.
 197:     /// </param>
 198:     /// <param name="userName">
 199:     /// The login for a user who has administrator permissions on the
 200:     /// specified blog.
 201:     /// </param>
 202:     /// <param name="password">
 203:     /// The user's password.
 204:     /// </param>
 205:     /// <param name="templateText">
 206:     /// The text for the new template.
 207:     /// </param>
 208:     /// <param name="templateType">
 209:     /// <list>
 210:     /// <listheader>
 211:     /// <term>Template type</term>
 212:     /// <description>Description</description>
 213:     /// </listheader>
 214:     /// <item>
 215:     /// <term>main</term>
 216:     /// <description>
 217:     /// Changes the text for the main template of the blog.
 218:     /// </description>
 219:     /// </item>
 220:     /// <item>
 221:     /// <term>archiveIndex</term>
 222:     /// <description>
 223:     /// Changes the text for the archive index template of the blog.
 224:     /// </description>
 225:     /// </item>
 226:     /// </list>
 227:     /// </param>
 228:     /// <returns></returns>
 229:     [XmlRpcMethod("blogger.setTemplate",
 230:         Description = "Changes the template for the specified blog.")]
 231:     bool SetTemplate(string appKey, string blogId, string userName,
 232:         string password, string templateText, string templateType);
 233: }

 

In my next post, we'll expand upon the Blogger API by implementing the MetaWeblog API which will give us the ability to publish to a website using Microsoft Office Word 2007 or Windows Live Writer.  We'll also start to look at implementing these operations to actually do something.

Technorati tags: , , , ,


Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Be the first to rate this post

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