Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

MetaWebLog API and Blog Writers


:P
On this page:

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>...               

 

Posted in ASP.NET  RSS  WebLog  

The Voices of Reason


 

Jeswin P
March 11, 2007

# re: MetaWebLog API and Blog Writers

Nice post as usual Rick.

As I was implementing Metaweblog API for ThinkJot (http://www.process64.com/thinkjot/) I came across some issues.

1. Metaweblog API targets a single blog. For instance, in the metaweblog_editPost method, there is no option to specify the blogid/blogname if you have multiple blogs. One workaround for this is to make the postid (which it takes) unique across all blogs. Another is to inject the MW API service url into every blog directory, and then identify the blog from the request url. Idea 1 will not work for me, since ThinkJot was allowing users to change content in their blog folders, which also included changing post-ids, voiding the uniqueness guarantee. Idea 2 was not working because a blog was allowed multiple urls.

Finally I decided to make the restriction that the username used to access MW had to be unique for a blog. ThinkJot was supporting multiple editors for a blog, and one of them had to be unique for that blog. Finally a compromise that worked.

.... to other MW API implementors, if you implement RSD (Really Simple Discoverability) offline clients like Windows Live Writer can automatically discover your preferred and supported APIs. This makes it easy to setup accounts.

Rick Strahl
March 11, 2007

# re: MetaWebLog API and Blog Writers

Yeah MetaWebLogApi seems to have an identity crisis <s>. Some methods like newPost do support a blogId while as you point out editPost does not, which is pretty silly.

Rick Strahl
March 11, 2007

# re: Word Blog Editing

I guess I spoke too soon with the Word Blogging feature. While all looks well in Word, when Word actually posts its HTML it's not posting the content correctly in many cases. This is especially true of the crucial code pasting issue - code pasted with VS.NET into Word then posted on the site got completely mangled as the fixed font formatting was completely lost.

I suspect Word actually uses the same HTML 'Light' export mechanism that is used for saving from the file menu.

So much for using Word then other than cut and paste. I'm back onto Live Writer now and fixing up some of the Paste plugins to suit a little better...

Speednet
March 11, 2007

# re: MetaWebLog API and Blog Writers

Hey Rick,

Thanks for the useful and informative blog entry. I tried DLing the zip and it was blank. Are you seeing that also?

Rick Strahl
March 11, 2007

# re: MetaWebLog API and Blog Writers

Todd, my bad - I didn't put the file into the right place... fixed now.

Speednet
March 12, 2007

# re: MetaWebLog API and Blog Writers

Thanks Rick, worked fine now. Thanks again for a great post - I'm looking forward to going thru the code and then updating my blogging engine.

Allen
March 14, 2007

# re: MetaWebLog API and Blog Writers

Hi Rick,
I have built my own custom blog, using ASP.NET 2.0 and C#, and have been wanting to implement the MetaWebLogApi for a while now. This post helped me do that, thanks!
So recently I've been using various blogging tools (Windows Live Writer, BlogJet, Zoundrey and BlogDesk) trying to find a comfortable desktop blog publisher. With all the products I found that I could only view posts that had been published using that specific product. So i couldn't view posts that I had manually uploaded via my custom web UI.
This is hugely frustrating and I was wanting to know if this was specific to the tool (in which case all of the above mentioned tools have conveniently collaborated to use the same irritating behaviour) or if it was specific to the MetaWebLogApi (Which I doubt as I clearly implemented the getRecentPosts method myself to return all "recent" posts without any specific filter on whether they were posted using a tool or my web UI). So why this behaviour?
Of course my implementation of getRecentPosts may be suspect and I am busy trying to test that independantly of a specific blog tool, but i don't think this is the issue.

Rick Strahl
March 14, 2007

# re: MetaWebLog API and Blog Writers

Allen, you should be able to view/edit any posts - I know I can see my hand added coded. In the end it's just HTML that's being posted. Now it's possible that the tools cannot properly read the HTML or present it for editing since some Word generated HTML (for example) can be pretty hectic, but even for my previous posts I that do that it works.

IAC, it probably isn't much of an issue anyway once you're past the initial posts with the new writers...

Allen
March 15, 2007

# re: MetaWebLog API and Blog Writers

OK, so redfaced, I admit that it was my implementation of the getRecentPosts method that was at fault. I was always returning 0 posts regardless, but now it is working and I am able to see all posts in Windows Live Writer.

Bill Graziano
April 05, 2007

# re: MetaWebLog API and Blog Writers

Thanks! This was a fantastic start to something I've been meaning to do for a while.

Jeremy Thompson
May 09, 2008

# re: MetaWebLog API and Blog Writers

Thanks Rick!

I too dislike the new directories for images, its particularly annoying when working with a Virtual Path Provider.

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024