Chris Roberts

Using RenderTemplate for On-Page Widgets in Umbraco

• Posted in Umbraco

Much has been written about creating 'widgets' or sidebar panels in Umbraco.

In a recent project, we decided to implement our on-page widgets as 'pages' in the Umbraco content tree. This has a number of advantages. Users can be provided with a number of different templates and edit the content with familiar tools. It also means that we can create a central 'pool' of widgets which can then be added to pages using a content picker.

Implementation

Umbraco includes a handy method - RenderTemplate() - which allows you to render an entire page to a string. This effectively passes a request for the page through the entire MVC and Umbraco processing pipeline, so you can still make use of custom controllers, etc.

We wrote a macro which would render our 'widgets' and added a call to it from the template of any page which needed to include them:

@foreach (var widget in widgetContainer.Children.Where("Visible"))
{
    @MvcHtmlString.Create(library.RenderTemplate(widget.Id))
}

Problems (and How To Fix Them)

RenderTemplate Throws an Exception

The first problem we encountered with this implementation was a potential show-stopper. Our seemingly simple call to RenderTemplate conistently threw the following exception:

<!-- Error rendering template with id 1112: 'System.ArgumentNullException: Value cannot be null. Parameter name: controller   
at System.Web.Mvc.ControllerContext..ctor(RequestContext requestContext, ControllerBase controller)   
at Umbraco.Web.Templates.TemplateRenderer.ExecuteTemplateRendering(TextWriter sw, PublishedContentRequest contentRequest)   
at Umbraco.Web.Templates.TemplateRenderer.Render(StringWriter writer)   
at Umbraco.Web.UmbracoHelper.RenderTemplate(Int32 pageId, Nullable`1 altTemplateId)' -->

This turned out to be a bug in Umbraco. Luckily a fix was available in the nightly builds. This bug fix was later introduced in Umbraco v6.1.3.

Referencing the Containing Page from a 'Widget' Page

Our particular requirements also led to another problem with this implementation. As each 'widget' is rendered as its own page (processing pipeline and all), it has no appreciation of the fact that it is being requested from within another page. There appears to be no inherent way of referring to the page which made the call to RenderTemplate.

After quite a bit of searching for 'out of the box' solutions in Umbraco, we ended up with the following:

@if (HttpContext.Current.Items.Contains("callingPageID"))
{
    HttpContext.Current.Items["callingPageID"] = Model.Content.Id;
}
else
{
    HttpContext.Current.Items.Add("callingPageID", Model.Content.Id);
}

@foreach (var widget in widgetContainer.Children.Where("Visible"))
{
    @MvcHtmlString.Create(library.RenderTemplate(widget.Id))
}

Prior to the call to RenderTemplate, we simply add the ID of the current page to the HttpContext. As the same context is used for the subsequent request to render the widgets, they can retrieve the ID from the HttpContext. This can then be used to retrieve the page and any of its properties, as required.