A runtime for Umbraco - Part 2

So before I start out on Part 2 - I should point out you need to read part 1 for this to make any sense. Also thanks for the twitter reaction, we even have a PHP implementation of the runtime now by Gary at https://github.com/garydevenay/Moriyama-Runtime-PHP

I should also point out that the interfaces detailed in part 1 are not part of the Umbraco core - I just hook into the regular Umbraco events to call my own implementations of my custom interfaces.

I can't fight this feeling any longer

At the end of part 1, I had all of my Umbraco content serialised to disc ready to push to a webapp that didn't have any Umbraco dependencies. But this whole system would be a little useless if I couldn't view my pages from within Umbraco before pushing them to the runtime.

Fortunately Umbraco has excellent support for registering custom routes and overriding the default Umbraco routing:

namespace Moriyama.Runtime.Umbraco.Events
{
    public class RuntimeApplicationEventHandler : IApplicationEventHandler
    {
        private static readonly ILog Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        public void OnApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
        {
            DefaultRenderMvcControllerResolver.Current.SetDefaultControllerType(typeof(RuntimeUmbracoController));
        }

An IApplicationEventHandler is discovered by Umbraco upon start up and Umbraco provides the SetDefaultControllerType method that allows you to use your own controller to handle requests to the front end website. This means that you can return views using any kind of Model that you wish.

So we'll take a look at the definition of RuntimeUmbracoController:

using System.Web.Mvc;
using Moriyama.Runtime.Controllers;
using Umbraco.Web.Models;
using Umbraco.Web.Mvc;

namespace Moriyama.Runtime.Umbraco.Controllers
{
    public class RuntimeUmbracoController : RenderMvcController
    {
        private readonly RuntimeController _runtimeController;

        public RuntimeUmbracoController()
        {
            _runtimeController = new RuntimeController();
        }

        [OutputCache(CacheProfile = "Standard")]
        public override ActionResult Index(RenderModel model)
        {
            return _runtimeController.Index();
        }
    }
}

At first this seems a bit odd, it is really just a proxy to another controller. I do this because the default Umbraco Controller needs to inherit from RenderMvcController so when we detach from Umbraco we need to remove that dependency and just use a regular MVC controller.

RuntimeController looks like this:

using System;
using System.Web.Mvc;
using Moriyama.Runtime.Models;

namespace Moriyama.Runtime.Controllers
{
    public class RuntimeController : Controller
    {
        public ActionResult Index()
        {
            var ctx = System.Web.HttpContext.Current;
           
            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));
        }

        private RuntimeContentModel Build404Model(Uri url)
        {
            var homeUrl = url.Scheme + "://" + url.Host + ":" + url.Port + "/";
            var content = RuntimeContext.Instance.ContentService.GetContent(homeUrl);

            if (content == null)
            {
                content = new RuntimeContentModel();
                content.Name = "404 Not Found";
            }

            return content;
        }
      
    }
}

From the above you can see how we render a page based on a template - just like Umbraco does, but using the RuntimeContentModel defined in part 1. But the question that the controller raises is what is ContentService and how does it use our JSON.

You give my life direction

Before I delve into the content service I need to quickly explain the structure of the Visual Studio solution:

And even as I wander I'm keeping you in sight

So back to the ContentService - more specifically:

using System;
using System.Collections.Generic;
using System.Web;
using Moriyama.Runtime.Models;

namespace Moriyama.Runtime.Interfaces
{
    public delegate void ContentAddedHandler(RuntimeContentModel sender, EventArgs e);
    public delegate void ContentRemovedHandler(string sender, EventArgs e);

    public interface IContentService
    {
        event ContentAddedHandler Added;
        event ContentRemovedHandler Removed;

        void AddContent(RuntimeContentModel model);
        void RemoveContent(string url);

        IEnumerable GetUrlList();

        RuntimeContentModel GetContent(HttpContext context);
        string GetContentUrl(HttpContext context);

        RuntimeContentModel GetContent(string url);
        RuntimeContentModel Home(RuntimeContentModel model);

        IEnumerable TopNavigation(RuntimeContentModel model);
        IEnumerable Children(RuntimeContentModel model);

        IEnumerable Descendants(RuntimeContentModel model);
        IEnumerable Descendants(RuntimeContentModel model, IDictionary<string, string> filter);

        RuntimeContentModel CreateContent(string url, IDictionary<string, object> properties);
    }
}

There is lots in here ahead of part 3 but for now the significant part is that it can get content based on a URL - specifically RuntimeContentModel GetContent(string url);

For those of you familiar with writing Umbraco templates you should also see the potential to write extension methods for Descendants etc, which we need for a nice fluent template authoring experience.

It's time to bring this ship into the shore

To conclude part two - I'll mention that there are a few reference implementations of IContentService:

As we are still previewing pages in Umbraco at this stage we'll normally stick with the CacheLessRuntimeContentService and it works for small websites too, but as we move forward, this service can be swapped out in configuration - and the deployed runtime app would likely be Lucene based or memory cached.

And throw away the oars

In part 3 - I'll either talk about the implementation of the CacheLessRuntimeContentService or talk about how to extend RuntimeController to do more complex things than render a piece of content. Obviously on this blog we have comments, and search and we need more than our RuntimeContentModel to send data back and forth.

It'll depend on the mood I'm in next time I sit down to write.

Forever

Thanks for reading and apologies for the not completely random headings. And I'm not withholding code, DM me on Twitter if you'd like access to the repo. I need to tidy lots to make it all readable :)

Comments

Leave a comment