Loading...
Supporting MetaWeblog

Supporting MetaWeblog

When i started out writing my new blogging system, two features I really wanted to support. One was writing the posts in Markdown, the other was to support submitting the posts using MetaWeblog so I could support writing the posts in editors. I had this feature back in the "good old days", when using a different blogging system.

Markdown I started supporting a while ago, now was the time to support MetaWeblog.

MetaWeblog in C#

I started looking around the web; someone must have done a nice clean implementation of MetaWeblog (or at least XML-RPC!); however the only one I could find haven't been updated in more than a decade and has no support for using it directly in MVC.

I started building a simple parser and ended up extending it quite drastically to ensure it would conform with the XML-RPC standard.

Then on top of that, I build a custom XmlRpcResult class which implements FileResult from MVC. This allows me to make Actions in MVC returning a nice strongly-typed implementation of the object and do XML conversion as part of the framework.

Kenc.XmlRpc Library

The end result? A small .net library compliant with XmlRpc. Got a few things to sort out first; then I will look into my options of supplying it on GitHub.

Routing in MVC

The routing is not quite where I want it yet; in the future I will support the actual routing capabilities of MVC; for now all calls to the URL will automatically be forwarded to one controller.

/// <summary>
/// Initializes a new instance of the <see cref="MetaWeblogController"/> class.
/// </summary>
public MetaWeblogController()
{
    this.actions = new Dictionary<string, Func<XmlRpcRequest, Task<object>>>
        {
            { "newPost", this.NewPost },
                { "getPost", this.GetPost },
                { "getUsersBlogs", this.GetUsersBlogs },
                { "getRecentPosts", this.GetRecentPosts },
    };
}

/// <summary>
/// Handle all requests and routing.
/// </summary>
/// <returns><see cref="Task{XmlRpcResult}"/>.</returns>
public async Task<XmlRpcResult> Index()
{
    var deserializer = new XmlRpcRequestDeserializer();
    var request = (XmlRpcRequest)deserializer.Deserialize(Request.GetBufferedInputStream());
    var response = new XmlRpcResponse();

    try
    {
        var func = this.actions.Where(x => x.Key == request.MethodName).Select(x => x.Value).FirstOrDefault();
        if (func == null)
        {
            throw new MissingMethodException(request.MethodObject, request.MethodName);
        }

        response.Value = await func(request);
    }
    catch (XmlRpcException exception)
    {
        response.SetFault(exception.FaultCode, exception.FaultString);
    }
    catch (Exception)
    {
        response.SetFault(XmlRpcErrorCodes.APPLICATION_ERROR, XmlRpcErrorCodes.APPLICATION_ERROR_MSG);
    }

    return new XmlRpcResult(response);
}

Not the cleanest routing, but sufficient for now.

Dealing with authentication

For my blog i use the capabilities of Owin; I have a local user which has a username and password, but primarily use my Microsoft account to login with. MetaWeblog only supports supplying username and password, so I had to do an implementation that ensured I would authenticate (since you can edit and create blog posts..)

    /// <summary>
    /// Authenticate the user for RPC.
    /// </summary>
    /// <param name="username">Supplied username.</param>
    /// <param name="password">Supplied password.</param>
    /// <returns>Returns a <see cref="Task"/> of the authentication.</returns>
    private async Task Authenticate(string username, string password)
    {
        if (string.IsNullOrEmpty(username))
        {
            throw new ArgumentException("Username cannot be empty.", "username");
        }

        if (string.IsNullOrEmpty(password))
        {
            throw new ArgumentException("Password cannot be empty.", "password");
        }

        var signinManager = HttpContext.GetOwinContext().Get<ApplicationSignInManager>();

        var result = await signinManager.PasswordSignInAsync(username, password, false, shouldLockout: false);
        if (result != SignInStatus.Success)
        {
            throw new Exception("Invalid login attempt.");
        }
    }

Simple implementation

The above allows for super simple implementation of the supported calls.

    /// <summary>
    /// Retrieves a single post.
    /// </summary>
    /// <param name="request">An instance of the <see cref="XmlRpcRequest"/> request.</param>
    /// <returns>An async task with the resulting object.</returns>
    private async Task<object> GetPost(XmlRpcRequest request)
    {
        var username = (string)request.Params[1];
        var password = (string)request.Params[2];
        await this.Authenticate(username, password);

        var guid = Guid.Parse((string)request.Params[0]);

        var blogSystem = BlogController.GetBlogSystem();
        var post = blogSystem.GetPost(guid);
        return post.ToMetaWeblogDictionary();
    }

I do expect to move the authentication part out of each function, as the MetaWeblog has consistency in which parameters are username and password.

Enjoying the luxury of an editor

With all the above implemented, I can start using editors such as MarkPad

Writing this post in MarkPad

There are 0 comments

Add your comment