In this posts, I'll be working through the process of migrating this blog from its previous host platform - dotnetblogengine - to its current one - Ghost(pro). I see a different email for this every year and the last one was in May 2017 from Brian Peek. However a lot has happened to Ghost since May 2017 - they hit v1 for one thing in July '17 and the software is currently at v1.2* - so here's an update.

First, a bit of context. This blog has been around a while. Back in 2000 when it began, it was just a bunch of html files hosted on geocities updated evry day via ftp. It has been powered by various .net blog engines - subtext, dasblog, dotnetblogengine, and miniblog - as well as blogger and wordpress, but now it is moving to Ghost. Why Ghost? Mostly the question is why not? Mature blog platforms like Wordpress and Joomla aim well beyond the humble user such as yours truly. I'm simply looking for an intuitive user experience (or at least one I recognize) with a convenient way to write from the desktop, a convenient way to try a few things locally, and a low maintenance requirement. Happily Ghost fits all three of those things.

  • Ghost's desktop client app is pretty straightforward and reminiscent of (a monochrome) Windows Live Writer (OpenLiveWriter as it now is).
  • Sitting on top of Node, it is relatively pain free to get a test site running on your local machine to play with and see if it is your cup of tea. You install node, then their Ghost-CLI app, then call ghost install in a terminal window.
  • Your maintenance level is as you choose it. The Ghost team officially support live-in-production installations only on Ubuntu, but you can find Ghost in the Azure Marketplace now and it just works. Or, like me, you can cede maintenance and upgrade issues to the Ghost team themselves and pay then to host your words on their Ghost(Pro) service. The Ghost team are also pretty responsive to queries on the forum and directly to their support email. Big shout out to Sarah who certainly helped me out a few times when the existing docs fell a little short.

Preamble over then, there are five basic steps to porting your blog from dotnetblogengine to Ghost, or indeed any blog that exports its content as BlogML.

  1. Export posts from current blog to blogml
  2. Convert those posts from blogml to ghost.json format
  3. Move files and photos
  4. Import your posts
  5. Setting URL redirects

Let's go through these step by step.

Exporting your posts into BlogML

BlogEngine happily exports your posts into it's own slightly customized version of BlogML. (It adds an attribute to posts covering whether they have been published or not). To download the blogml for your posts, navigate to /admin/#/settings/tools/export or /admin/#/settings/advanced depending on your version of BlogEngine. On this page, you'll find a button to export your data to the BlogML format. Click the Export button to download the file, and save it somewhere locally.

Converting from BlogML to Ghost.json format

The most tricksy part of the process is converting your posts into the JSON format that Ghost understands for importing. The docs detailing the format of the JSON date back to v0.04 of Ghost and things have changed a bit since then. I'll cover in detail the current schema in a separate post, but you'll find the tool I wrote to make the conversion on Github at https://github.com/hmobius/BlogML2Ghost.Core. Just clone the repo, open it in Visual Studio (Code), call dotnet run and follow the prompts. It assumes that all the posts are written by a single author and should be imported as drafts rather than published immediately. If you'd like me to improve the app, let me know. The app will generate a file called Ghost.json but you may want to tweak it a little more if you have images or other files in your old blogengine....

Dealing with your files and photos

By default, ghost accepts only images - and just jpg, gif, svg and png too - for upload to its site. Ghost hosts all its images in the /content/images/ directory. You'll need to grab your images from blogengine's app_data/files directory and drag them to your new instance's /site/wwwroot/content/images folder if you're happy to just move everything. Or if like me you think a little editing and awful picture removal is in order, you may want to republish and re-up your images after you've imported your posts as drafts.

If you use the site or desktop app to write new posts with reference to zip files, word or excel files for instance, you'll need to host them elsewhere and then link to them in your post. That said, it is possible to host your non-image files within a Ghost site - it's just not something you can continue to do without continual hassle post import. Just create a new directory inside /site/wwwroot named public and copy those files here. These files will be available externally at the root of your site. For example, if you have a file named MyFile.txt located in /site/wwwroot/public, it would be available at http://yoursite.com/MyFile.txt.

However you've chosen to deal with your files, you'll need to go into your new ghost.json file and update the location of your images and files accordingly. If you're coming from BlogEngine, that means

  • Find /file.axd?file= and replace with /
  • Find /image.axd?picture= and replace with /content/images/

The former search repoints the files and the latter repoints the images.

Import your posts

Finally you can import your posts into Ghost. Find the Labs tab in your admin site or in the Ghost desktop app. Top of the page you'll see Import Content where you can choose your new ghost.json file and hit Import. Being a wise person you may want to test this on a local installation before committing it to live to make sure all the dots are connected. The error messages if something is wrong aren't the most forthcoming and the Delete All Content button just below in the Labs page deletes only posts and tags. Crucially it doesn't delete users that may have been imported with your posts which means you either have to fiddle with the import file to not reimport the user twice or with all the posts' author ids because once imported a user id changes from an integer to a guid which you have to find by exporting the site's content first (also on the labs page).

Adding Redirects

Last but not least, you'll want to create a couple of URL redirects so all those links from Stack Overflow, Facebook and the like still point to the right post and not the dreaded 404 (which incidentally you can properly tart up on Ghost - here's mine for instance). Posts on ghost all have URLs of the form https://sitename/post-slug-name/. If that matches your previous blogengine's post URLs great. dotnetblogengine's are of the form https://sitename/post/yyyy/mm/dd/post-slug-name.aspx or https://sitename/page/yyyy/mm/dd/post-slug-name.aspx

Recently, the Ghost Labs admin tab has added a Redirects feature where you can put your needed redirects in a JSON file and upload them. For example, here's my redirect

[{
  "from":"^/post/([0-9]{4})/([0-9]{2})/([0-9]{2})/([a-z0-9-]+)(\\.aspx)?(\\/)?$",
  "to":"/$4"
 },
 {
  "from":"^/page/([0-9]{4})/([0-9]{2})/([0-9]{2})/([a-z0-9-]+)(\\.aspx)?(\\/)?$",
  "to":"/$4"
 }
]

This works fine on Ghost(pro) but of course if you are hosting Ghost yourself, you may want to invoke your redirects through a piece of middleware or server config file.

And that's about it really. You can of course then find yourself a nice theme for the site - they are somewhat nicer than those for blogengine :-) - and link in your Google Analytics, disqus and social media accounts as you need.

Hope this helps. Any feedback \ requests on the importer tool, please raise them as an issue on the github page.