Got a little time last night to add MetaWebLogApi to my Blog in hopes of being able to start using slightly more favorable HTML in my posts. I’ve been using Word for all of my blogging over the years and while I’ve tried various tools for publishing posts I’ve always come back to the convenience of using Word for post editing.
As it turns out implementing MetaWebLogApi is not terribly difficult and it’s been done before in just about every Blog engine out there. I frequently browse through the open source SubText code base since I had been using .Text in the past. SubText is the extension of .Text and is seeing lots of enhancements under the stewardship of Haacked. Phil’s been adding a ton of great functionality and although I don’t use .Text anymore in favor of a home grown blog I still keep an eye on SubText for ideas on what to add to my own blog .
SubText includes a MetaWebLogApi implementation based on the RPCXML implementations from Cooke Computing which seems to be the defacto standard implementation that every bit of .NET XmlRpc code that I’ve seen is using. The XmlRpc classes are the key to get this to work.
Here’s a quick overview of what you need to do to implement MetaWebLogApi in .NET:
1. Create the MetaWebLogApi message structures and main Service Interface
2. Implement the MetaWebLogApi’s Interface as an XmlRpcService with your application specific logic
3. Hook up the service as an HttpHandler that exposes XmlRpcService exposes
1. Create the Interface and Structure classes
Using the Cooke Computing XmlRpc classes the implementation requires implementing a MetaWebLogApi Interface that exposes each of the methods of the MetaWebLogApi spec. I don’t want to post code here since it’s not mine to post <s>, but the easiest way to get this stuff is to grab it from SubText or other open source blog engine (dasBlog is another). The process for implementing XmlRpc is similar to implementing a Web Service with the difference that you need to implement everything explicit (hey, it’s contract first <s>) including messages which are the post content and the mediaElement values for handling image uploads. Message objects are implemented as structs rather than classes and simply map each of the message members. All in all you have the IMetaWebLog interface with its minimum of 3 methods (newPost,getPost,editPost) and several additional support methods (mediaObject,getCategories,deletePost, getRecentPosts,getUsersBlogs), and several message object implementations (Post, mediaObject, mediaObjectInfo,Category,CategoryInfo,BlogInfo).
The XmlRpc Interface definition look like this:
public interface IMetaWeblog
{
[XmlRpcMethod("metaWeblog.editPost", Description = "Updates and existing post to a designated blog " +
"using the metaWeblog API. Returns true if completed.")]
bool editPost(
string postid,
string username,
string password,
Post post,
bool publish);
… more interface methods
}
Which should look familiar if you’re familiar with Web Services and its attribute based model of markup. And the message structures look like this:
[XmlRpcMissingMapping(MappingAction.Ignore)]
public struct Post
{
[XmlRpcMissingMapping(MappingAction.Error)]
[XmlRpcMember(Description = "Required when posting.")]
public DateTime dateCreated;
[XmlRpcMissingMapping(MappingAction.Error)]
[XmlRpcMember(Description = "Required when posting.")]
public string description;
[XmlRpcMissingMapping(MappingAction.Error)]
[XmlRpcMember(Description = "Required when posting.")]
public string title;
public object postid;
public string[] categories;
public Enclosure enclosure;
public string link;
public string postabstract;
public string permalink;
public Source source;
public string userid;
}
Each field of the XML message is represented in its proper type. Notice that PostId is passed as an object – which is a way to allow variable types to be sent – for example postId can be both a string or an int per spec as can the blogId in some of the other methods (more about that in a minute). One nice thing about XmlRpc is by using the MappingAction.Ignore, the structure doesn’t have to provide all of the member values – so if link or source or abstract are missing no error occurs and in a way this provides for extensibility. Unfortunately the client tool has to actually sent any extended values in order for this to happen.
2. Create your application specific Implementation of the Interface
Once the Interface and classes are defined all that’s left to do is implement the actual MetaWebLogApi interface and inherit from XmlRpcService. For only posting these three methods need to be implemented. Here are the minimum methods you need to implement to get posting to work, but you’ll want to implement all the methods if you want to work with any of the Blog clients:
namespace Westwind.WebLog
{
/// <summary>
/// MetaWebLogApi implementation for the West Wind Web Log.
/// Very simplistic and currently tested only in combination with
/// LiveWriter, Word Blogging and BlogJet.
///
/// This class implements IHttpHandler so all that needs to happen
/// is to map this class as a Handler in Web.config or subclass
/// on an ASHX page.
/// </summary>
public class MetaWebLogApi : XmlRpcService, IMetaWeblog
{
/// <summary>
/// Validates the user and throws exception on failure which will throw
/// us out of any service method and return the error to the client.
/// </summary>
/// <param name="Username"></param>
/// <param name="Password"></param>
/// <returns></returns>
private bool ValidateUser(string Username, string Password)
{
busUser User = WebLogFactory.GetUser();
if (!User.ValidateUser(Username, Password))
// *** Throw exception here
throw new XmlRpcException("Invalid login information");
return true;
}
#region IMetaWeblog Members
public string newPost(object blogid, string username, string password, Post post, bool publish)
{
this.ValidateUser(username, password);
busEntry Entry = WebLogFactory.GetEntry();
Entry.New();
Entry.Entity.Title = post.title;
Entry.Entity.Body = post.description;
Entry.Entity.Author = post.userid;
if (post.dateCreated.Year > 2000)
Entry.Entity.Entered = post.dateCreated;
else
Entry.Entity.Entered = DateTime.Now;
if (post.categories != null)
{
string Categories = "";
foreach (string Category in post.categories)
Categories += Category + ",";
Entry.Entity.Categories = Categories.TrimEnd(',');
}
if (!Entry.Save())
throw new XmlRpcException("Failed to save new entry: " + Entry.ErrorMessage);
return Entry.Entity.Pk.ToString();
}
public mediaObjectInfo newMediaObject(object blogid, string username, string password, mediaObject mediaobject)
{
this.ValidateUser(username,password);
string ImagePhysicalPath = HttpContext.Current.Server.MapPath(App.Configuration.WebLogImageUploadPath);
string ImageWebPath = wwWebUtils.ResolveServerUrl(App.Configuration.WebLogImageUploadPath);
string Url = "";
if (mediaobject.bits != null)
{
MemoryStream ms = new MemoryStream(mediaobject.bits);
Bitmap bitmap = new Bitmap(ms);
ImagePhysicalPath = ImagePhysicalPath + mediaobject.name;
string PathOnly = Path.GetDirectoryName(ImagePhysicalPath).Replace("/","\\");
if (!Directory.Exists(PathOnly))
Directory.CreateDirectory(PathOnly);
bitmap.Save(ImagePhysicalPath);
}
mediaObjectInfo mediaInfo = new mediaObjectInfo();
mediaInfo.url = ImageWebPath + mediaobject.name;
return mediaInfo;
}
public BlogInfo[] getUsersBlogs(string appKey, string username, string password)
{
if (!this.ValidateUser(username, password))
return null;
BlogInfo blog = new BlogInfo();
blog.blogid = "0";
blog.blogName = App.Configuration.WebLogTitle;
blog.url = App.Configuration.WebLogHomeUrl;
return new BlogInfo[1] { blog };
}
… Additional interface methods not shown here
}
}
This class is obviously implementation specific – here my business objects are doing most of the work of retrieving and updating the data for the metaweblogApi code. The implementation of these methods is pretty trivial.
3. Hook up an HTTP Handler to your Implementation
You’ll notice that the implementation class is created like this:
public class MetaWebLogApi : XmlRpcService, IMetaWeblog
XmlRpcService implements IHttpHandler, so this class that you created is ready to be used as an HTTP handler. There are several ways to hook up the handler:
Create an ASHX file and set the class to inherit from MetaWebLogApi
In the codebehind (or in the ASHX file itself you can simply implement the class like this: that there’s a file in the application that
public class MetaWebLogApiHandler : MetaWebLogApi {}
I like this approach mainly because it leaves a file as a reminder as to what’s happening in the application’s Web directory which is a little more obvious than a web.config hooked handler.
Add the Handler to web.config
Alternately you can add the handler in web.config with your class and assembly name:
<httpHandlers>
<add verb="POST" path="MetaebLogApi.ashx" type="Westwind.Weblog.MetaWebLogApi,WeblogFramework"/>
</httpHandlers>
And that’s it. I’ve zipped up the code I’ve used with this application specific implementation here:
http://www.west-wind.com/files/tools/misc/metaweblog.zip
The code is self contained, but you’ll need to replace the implementation code in the MetaWebLogApi.cs file.
Testing with various Writers
I’ve experimented in the past with a variety of different offline editors, but I’ve always ended up returning to using plain Word for posting and then pasting the text into the online editor. The big problem with this approach is that Word really creates the most unappealing and verbose HTML you can imagine which bloats the HTML considerably and triggers any HTML validation tool to have a heart attack.
I got back onto checking this out after a post from K. Scott Allen where he’s using MetaWebLogApi to capture output from the more friendly Word Blogging feature. I had played with this during the Office 2007 beta and it was not working very well and really hosed images sent to the server (bloating image sizes horribly), but after taking another look today I was happily surprised with the output created by the Word blogging plugin and the way it handles images.
Scott ran into a little problem with the XmlRpc calls generated by Word when it sends its requests to the server – specifically several requests send a BlogId that identifies to the server which blog is to receive the request. The spec names this value as a string value but Office – in some of the methods, but not all of them – sends an Int value which breaks the RPC interface. I ran into the same problem when I experimented with the Word posts, but the solution is actually quite easy: Instead of setting the value to a concrete type in the MetaWebLogApi Interface, using an object parameter allows receiving the value in a consistant fashion. So instead of:
[XmlRpcMethod("metaWeblog.newPost",
Description="Makes a new post to a designated blog using the "
+ "metaWeblog API. Returns postid as a string.")]
string newPost(
string blogid,
string username,
string password,
Post post,
bool publish);
using:
[XmlRpcMethod("metaWeblog.newPost",
Description = "Makes a new post to a designated blog using the " +
"metaWeblog API. Returns postid as a string.")]
string newPost(
object blogid,
string username,
string password,
Post post,
bool publish);
and then implementing the concrete method also with an object parameter. blogID then always comes in as an object /string type. The spec seems to indicate string values for all Ids but to be safe objects and casting to string are probably safer with various readers.
The Word functionality works well for me especially since I’ve been already using Word for Blog editing. The export generates much nicer HTML to be sure, which is both leaner, easier to read and will validate a bit better (although not perfect by any means – the purisets will still scream). An additional reason to use a tool like this in the first place is to get automatic image upload. Currently what I do is use SnagIt to capture images and FTP them, and then link the images from the Web into the Word doc which then works correctly when pasted to the server. Using an offline client makes that process a bit easier by automatically posting images to the server.
I also played a bit with Windows Writer. Windows Writer is also very nice and is a more dediciated environment for blogging. From a workspace perspective I prefer that interface. Writer also has a plug-in interface along with a very vibrant plug-in community that’s added a ton of functionality already to Writer. There are lots of interesting plug-ins that are useful like book links, rich image links.
However, there are also a few things that I don’t like in Windows Writer:
· Categories are fixed – you can’t add a new category
· Image uploads create multiple images always
· Image uploads go into new directories always
· Inserting of code requires a plug-in
It’s really great that Live Writer has an easy plug-in model but man do I wish there was a bigger API to this thing so you could custom fields. As I mentioned above XmlRpc supports expanded elements on the server so if new post elements are added to a post from the client it won’t break the server. But I hate the fact that MetaWebLogApi doesn’t support all the things the server does – Post Abstract Text for example and Keywords or simply the fact that you can’t add additional categories. It would be really nice if you had control over the outgoing messaging API to add functionality as the post is sent to the server… wishful thinking.
My current process even with a blog publishing tool will be: Publish the post then jump to the Admin interface and add several of the fields manually there. Heck I can’t even get these tools to directly jump to my Admin URL because there’s no way to configure how to show the current post. These are little things that would be so easy to address, but without extensibility hooks that’s no go…
What makes Word and even Live Writer nice are the editors used. Html Editing is difficult as hell to do and you can see this even in well established tools like BlogJet which IMHO opinion suck for the editing experience. This is just one more example why it would be really useful for Microsoft to make available a decent HTML editing interface – it certainly wouldn’t be hard to build a nice front end Writer like application, but the sticking point is the Html Editing which I would never get right <s>.
So over the next few days I guess I'll be experimenting a bit <s>...
Other Posts you might also like