A runtime for Umbraco - Part 3

And so on to part 3 - and the runtime has a name "Speedwagon".

Since I last wrote, the project has become all hipster with Kevin offering up a Node implementation of the runtime at https://github.com/KevinJump/nodebraco.runtime.site

I've also bared my soul and published the source for the runtime at: https://github.com/darrenferguson/moriyama-umbraco-runtime

This time, I want to write about the CacheLessRuntimeContentService and show how we can get a piece of content from our  JSON files and make it available for an MVC view to render.

The default runtime controller does this:

var url = ctx.Request.Url;
var urlString = String.Format("{0}{1}{2}{3}", url.Scheme, Uri.SchemeDelimiter, url.Authority, url.AbsolutePath);

var model = RuntimeContext.Instance.ContentService.GetContent(urlString);

if (model != null)
    return View("~/Views/" + model.Template + ".cshtml", model);

Response.StatusCode = 404;
return View("~/Views/404.cshtml", Build404Model(ctx.Request.Url));

Simplified into plain English this just means here is a URL, get me some content.

The CacheLessRuntimeContentService gets content from disc every time you ask for it. it also acts as a base class for other implementations of the content service which override certain methods to check in a content cache etc.

public virtual RuntimeContentModel GetContent(string url)
    url = ProcessUrlAliases(url);

    Logger.Info("Got from disk " + url);
    var contentFile = PathMapper.PathForUrl(url, false);

    if (!File.Exists(contentFile))
        if (Removed != null)
            Removed(url, new EventArgs());

        return null;

    var content = FromFile(contentFile);

    if (Added != null)
        Added(content, new EventArgs());
    return content;

The PathMapper is something I've mentioned previously which turns a URL into a path on disc and the FromFile method just deserialises JSON. ProcessUrlAliases just swaps out the URL of the request with the URL that Umbraco runs on.

In a cached content service GetContent is overridden to look in the Cache and fall-back to the base method in the cache less implementation.

So what is missing from Part 3 is traversal. I'm going to try and explain this in words, but it is probably best to peek into the source.

Children are just content items that have a URL that begin with the same URL but contain one more Slash. Descendants are just content items that begin with the same URL. Root content is just content with a level of 1. Top navigation is just content with a Level of two. The logic is all really simple.

To have access to this information, the runtime just maintains a file that contains a list of all URLs.

For example. in a View I can do:

foreach (var page in Model.Home().Children().Where(page => page.Type == "BlogTextPage" && !page.HideInNavigation()))

Under the hood Home does:

protected string HomeUrl(RuntimeContentModel model)
   var a = Urls.Where(x => model.Url.StartsWith(x)).OrderBy(x => x.Length);
   return a.First();

And Children does:

protected IEnumerable ChildrenUrls(RuntimeContentModel model)
    return Urls.Where(x => x.StartsWith(model.Url) && x != model.Url && x.Split('/').Length == model.Url.Split('/').Length + 1);

There are lots of efficiencies to be gained - but for now the logic is kind of readable.

So a wrap for part 3 I think. Part 4 will be how to extend the default Runtime Controller, to do posting of blog comments and other form submissions. Part 5, implementation of cached content services, and part 6 what needs to be done to speed this up and make it integrate seamlessly with Umbraco.

Thanks for your interest to date - I'd love your feedback on the source, but please go easy. I did rush it somewhat.


Leave a comment