DEV Community

Cover image for Extending the UmbracoHelper class in Umbraco v8
Tim Geyssens
Tim Geyssens

Posted on

2 2

Extending the UmbracoHelper class in Umbraco v8

While setting up an Umbraco site you'll probably end up with some "site settings" that will eather be on a top level node or on a child settings node. Think things like setting up the nav, the footer...

These are items you only set up once a site and want to manage in a global way.

You can of course write a query each time to traverse the tree... but something that v8 makes easy and possible is to extend the UmbracoHelper that is available in your Razor Views.

Imagine that you can simply do @Umbraco.WebSiteSettings.Footer and that you get the data you want (strongly typed). THis is nice and readable and folks without a lot of razor knowledge can also see what is happening...(maybe if a frontend dev needs to do some changes).

Ok let's get started. First of all, to do the stronlgy typed stuff, use Modelbuilders (duh).

I tend to use this config:

<add key="Umbraco.ModelsBuilder.Enable" value="true" />
<add key="Umbraco.ModelsBuilder.ModelsMode" value="LiveAppData" />
<add key="Umbraco.ModelsBuilder.ModelsDirectory" value="~/Models.Generated" />

Once generated you'll need to include the files in the vs project...

With that in place I now have strongly typed models of my document types...

Next is to get into the DI magic of V8, and build a custom Service

Define an interface (I called mine ISiteService)

using Umbraco.Web.PublishedModels;
namespace MyWebSite.Custom
{
public interface ISiteService
{
Website GetWebsite(int id);
SearchPage GetSearchPage(int id);
}
}
view raw ISiteService.cs hosted with ❤ by GitHub

It has 2 methods, 1 that fetches a Website doc based on id and one that will fetch the SearchPage doc based on id (both returning strongly typed models generated by ModelsBuilder)

The implementation looks like this

using System.Linq;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web;
using Umbraco.Web.PublishedModels;
namespace MyWebSite.Custom
{
public class SiteService : ISiteService
{
private readonly IUmbracoContextFactory _umbracoContextFactory;
public SiteService(IUmbracoContextFactory umbracoContextFactory)
{
_umbracoContextFactory = umbracoContextFactory;
}
public Website GetWebsite(int id)
{
Website website = null;
using (UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext())
{
website = umbracoContextReference.UmbracoContext.Content.GetById(id).AncestorOrSelf(Website.ModelTypeAlias) as Website;
}
return website;
}
public SearchPage GetSearchPage(int id)
{
SearchPage searchpage = null;
using (UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext())
{
searchpage = GetWebsite(id).ChildrenOfType(SearchPage.ModelTypeAlias) as SearchPage;
}
return alias;
}
}
}
view raw SiteService.cs hosted with ❤ by GitHub

So using the alias of the type. And then looks for the ancestor of the current fed in node id of that alias (since the website will always be at the top of the tree)

For the Searchpage, it first fetched the Website and then looks for the child of the correct alias (since it will be located directly under the website node)

Of course for all of this to work we need to register the custom service

using Umbraco.Core.Composing;
using Umbraco.Core;
namespace MyWebSite.Custom
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class RegisterSiteServiceComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Register<ISiteService, SiteService>();
}
}
}

And now we can inject it where we want...

But I'll also create an extension for the UmbracoHelper

using LightInject;
using Umbraco.Core;
using Umbraco.Web;
using Umbraco.Web.PublishedModels;
namespace MyWebSite.Custom
{
public static class UmbracoHelperExtensions
{
public static Website GetWebsite(this UmbracoHelper umbracoHelper)
{
var siteService = Umbraco.Web.Composing.Current.Factory.GetInstance<ISiteService>();
return siteService.GetWebsite(umbracoHelper.AssignedContentItem.Id);
}
public static SearchPage GetSearchPage(this UmbracoHelper umbracoHelper)
{
var siteService = Umbraco.Web.Composing.Current.Factory.GetInstance<ISiteService>();
return siteService.GetSearchPage(umbracoHelper.AssignedContentItem.Id);
}
}
}

Since it is static I can't use the standard constructor approach for injecting my custom service but I can still get to it with

Umbraco.Web.Composing.Current.Factory.GetInstance();

And with all that in place I can now access my service from the Umbraco Helper in views:

@if (Umbraco.GetWebsite().MainNavigation.Any())
{
<ul class="navbar-nav ml-auto">
@foreach (var link in Umbraco.GetWebsite().MainNavigation)
{
<li class="nav-item">
<a class="nav-link" href="@link.Url" target="@link.Target">@link.Name</a>
</li>
}
</ul>
}
@if (Umbraco.GetSearchPage() != null)
{
<form method="post" action="@Umbraco.GetSearchPage().Url">
<input name="q" type="text" />
<input type="submit" value="Submit">
</form>
}

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (7)

Collapse
 
dawoe profile image
Dave Woestenborghs • Edited

Hi Tim,

Getting the alias can be done a lot easier... just use Website.ModelTypeAlias

But in fact you don't need it. You can also do this :

website = UmbracoContextReference.UmbracoContext.Content.GetById(id).AncestorOrSelf<Website>();
Enter fullscreen mode Exit fullscreen mode

Personally I also would create a overload for the SiteService methods... where you can pass in a IPublishedContent so you can eliminate the the GetById call.

Collapse
 
timgeyssens profile image
Tim Geyssens

sweet, updating...

Collapse
 
mistyn8 profile image
Mike Chambers

I needed a htmlHelper to get the renderedGrid content from

using (UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext())
{
var currentContext = umbracoContextReference.UmbracoContext;
var developments = currentContext.Content.GetByContentType(DevelopmentPage.GetModelContentType()).First();
development.GetGridHtml(htmlHelper, "gridContent", "Bootstrap3-Fluid")
}

I ended up using public static class HtmlExtensions rather than UmbracoHelperExtensions as I didn't need the UmbracoHelper...
If I was to need both, is there a way to get both other than passing the one we haven't extended into the constructor?

Collapse
 
mistyn8 profile image
Mike Chambers

I had just created my own service, and was using Umbraco.Web.Composing.Current.Factory.GetInstance(); in loads of places.. (templates/partials) adding on to the umbracoHelper is so cool!

Would it be worth adding some caching for these ask once and remember globalish settings (dependant on publish)?
Just thinking you could have multiple partials on a single page all requiring to know the website root..
Or is dom traversal in Umbraco performant so don't need to worry?

Collapse
 
timgeyssens profile image
Tim Geyssens

yeah makes sense! But remember it is already using cache, so adding a cache layer on top of cache might have strange results... but can be tweaked :)

Collapse
 
fransdejong profile image
Frans de Jong

I always create a custom viewpage.

The 2 advantages in my opinion are:

  • no messing with static classes so DI is working
  • no confusion about what is Umbraco and what is custom code. The Umbracohelper is the same in every project and the viewpage SiteSettings object contains all the SiteSettings per site.
Collapse
 
timgeyssens profile image
Tim Geyssens

Nice, thanks for sharing!

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay