Hybrid Framework

Heads Up!

This article is several years old now, and much has happened since then, so please keep that in mind while reading it.

The Hybrid Framwork is a complete Umbraco Visual Studio solution which can be used as a basis to develop new MVC websites on.

I am Jeroen and I work as a developer at Have A Nice Day Online. Last year I showed the DAMP Gallery during the 24 Days In Umbraco Christmas Calendar. It was a complete website that showed how to use DAMP in MVC. That was a year ago and the way I'm using DAMP these days in MVC has changed. I'm using a special version of DAMP that can work together with CropUp. By adding a few extension methods it has become even easier to create a gallery. These changes are part of the Hybrid Framework. It's an open source Umbraco Visual Studio solution which can be used as a basis to develop new websites on. It also has a best practises example project which can be used the same way as the DAMP Gallery. The Hybrid Framework has the following features:

- MVC Framework
- Uses route hijacking, but not mandatory
- Everything is a surface controller
- Also has a default controller
- A lot of useful extension methods
- Preinstalled packages
- Configured settings
- Cache and bundling + minification

The following example (which is also part of the Hybrid Framework Best Practises) shows how easy it is to write a responsive image gallery. 

@using Umbraco.Web;
@using Umbraco.Extensions.Utilities;
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
    Layout = "Master.cshtml";
    //for slimmage to adjust quality based on pixel ratio, quality querystring must be present
    var gallery = Model.Content.GetCroppedImages("gallery", 400, null, quality:90, cropAlias:"gallery", slimmage:true);
}

<h1>@(Model.Content.GetPropertyValue<string>("title"))</h1>
@(Model.Content.GetPropertyValue<HtmlString>("bodyText"))

@*Gallery*@
@if (gallery.Any())
{
    <div class="gallery">
        <ul>
            @foreach (var photo in gallery)
            {
                <li>
                    <a href="@photo.Url" data-track="@photo.TrackLabel">
                        <img class="gallery-image" src="@photo.Crop" alt="@photo.Alt" />
                    </a>
                </li>
            }
        </ul>
    </div>
}

It's called the Hybrid Framework because it uses route hijacking (which means logic can be done in a controller before it passes a model to the view), but you don't have to do that. For example on a news overview page you can fetch the news items (child nodes) in the controller, but on a news details page you only need to show a few properies so that doesn't need a controller. The following code shows the new overview:

@using Umbraco.Extensions.Models;
@using Umbraco.Extensions.Utilities;
@using Umbraco.Web
@inherits Umbraco.Web.Mvc.UmbracoViewPage<NewsOverviewModel>
@{
    Layout = "Master.cshtml";
}

<h1>@(Model.Content.GetPropertyValue<string>("title"))</h1>
@(Model.Content.GetPropertyValue<HtmlString>("bodyText"))

<section class="news">
    @*News items overview*@
    @foreach (var n in Model.NewsItems)
    {
        <article class="newsitem">
            <a href="@n.Url">
                @n.Title<br />
                @n.Date.ToString("F")<br />
                <img src="@n.Image.Crop" alt="@n.Image.Alt" />
            </a>
        </article>
    }
</section>
@*Pager*@
@if (Model.Pager.Pages.Count() > 1) 
{
    <div class="pagination">	

	    <ul>
            @if (Model.Pager.IsFirstPage)
            {
                <li><a class="disabled">&lt;</a></li>
            }
            else
            {
                <li><a href="?page=@(Model.Pager.CurrentPage - 1)">&lt;</a></li>
            }

            @foreach (var number in Model.Pager.Pages)
            {
                var distanceFromCurrent = number - Model.Pager.CurrentPage;

                if (number == Model.Pager.CurrentPage)
                {
                    <li><a class="active">@number</a></li>
                }

                else if ((distanceFromCurrent > -10) && (distanceFromCurrent < 10))
                {
                    <li><a href="?page=@number">@number</a></li>
                }
            }

            @if (Model.Pager.IsLastPage)
            {
	            <li><a class="disabled">&gt;</a></li>
            }
            else
            {
                <li><a href="?page=@(Model.Pager.CurrentPage + 1)">&gt;</a></li>
            }

	    </ul>

    </div>
}

As you can see it's possible to use Model.NewsItems and Model.Pager, but also Model.Content.GetPropertyValue<string>("title"). That's because the NewsOverviewModel inherits from a base model which in turn inherits from the RenderModel again, which is the default model that Umbraco uses everywhere. The following code shows how the model is created in the controller:

using DevTrends.MvcDonutCaching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using Umbraco.Extensions.Controllers.Base;
using Umbraco.Extensions.Models;
using Umbraco.Extensions.Models.Custom;
using Umbraco.Extensions.Utilities;
using Umbraco.Web;
using Umbraco.Web.Models;

namespace Umbraco.Extensions.Controllers
{
    public class NewsOverviewController : BaseSurfaceController
    {
        [DonutOutputCache(Duration = 86400, Location = OutputCacheLocation.Server, VaryByCustom = "url;device")]
        public ActionResult NewsOverview()
        {
            var model = GetModel<NewsOverviewModel>();

            var newsItems = GetNewsItems();
            var pager = Umbraco.GetPager(2, newsItems.Count());

            //Only put the paged items in the list.
            model.NewsItems = newsItems.Skip((pager.CurrentPage - 1) * pager.ItemsPerPage).Take(pager.ItemsPerPage);
            model.Pager = pager;

            return CurrentTemplate(model);
        }

        public IEnumerable<NewsItem> GetNewsItems()
        {
            return
            (
                from n in CurrentPage.Children
                orderby n.GetPropertyValue<DateTime>("currentDate") descending
                select new NewsItem()
                {
                    Title = n.GetPropertyValue<string>("title"),
                    Url = n.Url(),
                    Image = n.GetCroppedImage("image", 300, 300),
                    Date = n.GetPropertyValue<DateTime>("currentDate")
                }
            );
        }
    }
}

As you can see all logic is done in the controller and the view has almost no logic. For the news items it's just a simple foreach.

The news details page doesn't need this kind of logic so it doesn't have a controller. The following code is everything which is needed for the news details:

@using Umbraco.Web;
@using Umbraco.Extensions.Models.Custom;
@using Umbraco.Extensions.Utilities;
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
    Layout = "Master.cshtml";
    var image = Model.Content.GetCroppedImage("image", 300, 300, quality:70);
}

<h1>@(Model.Content.GetPropertyValue<string>("title"))</h1>
<p>@(Model.Content.GetPropertyValue<DateTime>("currentDate").ToString("F"))</p>
<p>@(Model.Content.GetPropertyValue<HtmlString>("bodyText"))</p>
<p>
    <a href="@image.Url" target="_blank" data-track="@image.TrackLabel">
        <img src="@image.Crop" alt="@image.Alt" />
    </a>
</p>

Both news overview and news details inherit from the same master. Here are some parts of that master. 

@inherits Umbraco.Web.Mvc.UmbracoViewPage<BaseModel>

@*Menu*@
<nav>
    <ul>
    @foreach(var m in Model.MenuItems)
    {
        <li class="@m.ActiveClass"><a href="@m.Url">@m.Title</a></li>
    }
    </ul>
</nav>

Even though the news details page doesn't have its own model or controller we can still pass a BaseModel to the master because of the default controller:

using DevTrends.MvcDonutCaching;
using System;
using System.Web.Mvc;
using System.Web.UI;
using Umbraco.Extensions.Controllers.Base;
using Umbraco.Extensions.Models;
using Umbraco.Web.Models;

namespace Umbraco.Extensions.Controllers
{
    public class DefaultController : BaseSurfaceController
    {
        /// <summary>
        /// If the route hijacking doesn't find a controller this default controller will be used.
        /// That way a each page will always go through a controller and we can always have a BaseModel for the masterpage.
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [DonutOutputCache(Duration = 86400, Location = OutputCacheLocation.Server, VaryByCustom = "url;device")]
        public override ActionResult Index(RenderModel model)
        {
            var baseModel = GetModel<BaseModel>();
            return CurrentTemplate(baseModel);
        }
    }
}

If we don't route hijack to our own controller this controller will be hit. That means that the news details page will go through this controller which creates the BaseModel that can be used in the master view. Because the BaseModel inherits from the RenderModel the news details view also just works. All the controllers inherit from the BaseSurfaceController which makes sure that a single controller can be used for both route hijacking and surface controllers. Here are some parts of the controller:

/// <summary>
/// Return the base model which needs to be used everywhere.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="content"></param>
/// <returns></returns>
protected T GetModel<T>()
    where T : BaseModel, new()
{
    var model = new T();
    model.MenuItems = GetMenuItems();

    return model;
}

private IEnumerable<MenuItem> GetMenuItems()
{
    return
    (
        from n in CurrentPage.TopPage().Children
        where n.HasProperty("menuTitle")
        && !n.GetPropertyValue<bool>("hideInMenu")
        select new MenuItem()
        {
            Id = n.Id,
            Title = n.GetPropertyValue<string>("menuTitle"),
            Url = n.Url(),
            ActiveClass = CurrentPage.Path.Contains(n.Id.ToString()) ? "active" : null
        }
    );
}

So even all the logic which we need on the masterpage like a menu is done in a controller. This creates a perfect separation of HTML and logic. All the code above is a lot to explain, but it's the basic principle of the Hybrid framework.

If you look at the controllers in the code above you can see that the ActionResult methods all have the DonutOutputCache attribute. This means that the entire controller and view are cached, but parts of the view can be uncached. You can read more about it here: https://github.com/moonpyk/mvcdonutcaching. It makes all the pages extremly fast and because the default controller also has donut cache it means every page get's cached. There is an example on the contact page. You can read about how to use it outside of the Hybrid Framework here.

The hybrid framework contains even more things, but this blog post is getting long enough already. Below you can see the uHangout talk about the Hybrid Framework and I also did a talk at the Umbraco UK Festival 2013.

You can download the source code here: https://github.com/jbreuer/Hybrid-Framework-Best-Practices

And this last video shows how you can install the Hybrid Framework: 

Jeroen Breuer

Jeroen is on Twitter as