Configuring Umbraco 7 for MiniProfiler

This post is based on the following Umbraco setup.
  • Umbraco v7.2
  • Azure Sql Server – Web Edition

Why create this post.

During a previous v7.2 Umbraco project I tried several times and failed to configure Umbraco to use Mini Profiler. This seemed the simplest route to debugging some performance problems I was having with the site. However, there was no definitive guide out there on how to configure Mini Profiler that worked for me. So, to try and create that guide, and to save me searching across multiple blog posts and forums each time, I’ve created this,

What is Mini Profiler

Mini Profiler is a component from those clever bods over at Stack Exchange, and has been part of Umbraco since vx.xx When enabled, Mini Profiler provides diagnostics about your application in a

How to configure in Umbraco 7.

1. Add the following piece of Razor to the views you want to debug.
@MiniProfiler.RenderIncludes()
2. Add the following using statement to the top of all views
using StackExchange.Profilling

3. Ensure the debug attribute of the compilation element in the web.config in the root is set to true.

4. Open the page you want to debug in the a web browser and append ?umbDebug=true to the query string.

And that is it!

Filtering nodes in the Umbraco back office

This post is based on the following Umbraco setup.

  • Umbraco v7.2
  • Azure Sql Server – Web Edition

Question

Is there a way in the Umbraco back office to selectively filter tree nodes based on a property value on the user?

Answer

Sadly, no. There is currently nothing natively in the back office which will allow the conditional display of tree nodes. However using Umbraco’s event model and API, it is fairly simple to add this in.

Requirement

In this particular Umbraco installation, each back office user will be assigned to a country. In one area of the content tree, each node has a country property. The tree structure looked like this;

Answers
– German Answer 1
– French Answer 1
– USA Answer 1
– Australia answer 1
– French answer 1

If a French back office user logged in to the back office he should only see this;

Answers
– French answer 1
– French answer 2

The Solution

Of course I can take no real credit for this solution. Once again the excellent resource that is Our Umbraco provided the answers. I was aware that Umbraco used an event model, so the first piece of the puzzle was understanding which event to hook on to. The answer event I was looking for was;

TreeControllerBase.TreeNodesRendering

I created a TreeEvents class which inherited from ApplicationEventHandler. I then overrode the ApplicationStarted method to which I added the following event handler;

protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) 
{ 
 TreeControllerBase.TreeNodesRendering += FilterCountryNodes; 
}

The handler looked like this;

public static void FilterCountryNodes(TreeControllerBase sender, TreeNodesRenderingEventArgs eventArguments)
{
  if (sender.TreeAlias == Constants.UmbracoNative.CmsSections.ContentAlias)
  {
    var currentUserLevel = sender.Security.CurrentUser.UserType.Alias;
    if (currentUserLevel == Constants.ContentWorkflow.UserTypes.LowLevel)
    {
      var answerNodeIds = eventArguments.Nodes.Select(n => Int32.Parse(n.Id.ToString()));
      var umbracoHelper = new Umbraco.Web.UmbracoHelper(Umbraco.Web.UmbracoContext.Current);
      var allCountries = umbracoHelper.TypedContentAtXPath(Constants.XPaths.GlobeSearchCountries);

      var countriesUserCanEdit = TreeHelper.GetCountryIdsUserCanEdit(allCountries, sender.Security.CurrentUser.Id);

      var answers = sender.ApplicationContext.Services.ContentService.GetByIds(answerNodeIds);
      var nodesToRemove = TreeHelper.GetNodesToRemove(answers, countriesUserCanEdit);
      eventArguments.Nodes.RemoveAll(node => nodesToRemove.Contains(Int32.Parse(node.Id.ToString())));
    }
  }
}

To break this down a litle, the method firstly only acts if the type of Umbraco tree we are receiving the event from is the Content section. A tree in this case is considered to be any one of the Cms sections eg Content, Media, etc, including any custom sections you may have added. We are also only performing this filtering for a particular back office user type (LowLevel), so we inspect the user type alias property to make sure the logged in user is of the correct type.

The final piece of the puzzle was trying to associate a user to a country, and filtering the answers by the user’s countries. I started by trying to add countries to a back office user. However, unlike all other areas of Umbraco, the Users section and Users doesn’t appear to be extensible in any way, so another forum post in “Our” helped turn the problem on its head.

Adding users to a country

In Umbraco I have a Country document type defined. To this I added a new multi user picker property. This allowed me to associate multiple Umbraco back office users with a particular country.

In my event handler I check the logged in user has been added to the new property for each country.

var allCountries = umbracoHelper.TypedContentAtXPath(Constants.XPaths.SearchCountries);
var countriesUserCanEdit = TreeHelper.GetCountryIdsUserCanEdit(allCountries, sender.Security.CurrentUser.Id);

Once we have a list of the logged in user’s countries, we then retrieve the answers for all countries. We then find any answers which don’t contain at least one of the users countries – a user can be associated with multiple countries. Finally once we have a list of answers which the logged in user shouldn’t be able to view, we call the Umbraco API method RemoveAll()

var answers = sender.ApplicationContext.Services.ContentService.GetByIds(answerNodeIds); 
var nodesToRemove = TreeHelper.GetNodesToRemove(answers, countriesUserCanEdit); 
eventArguments.Nodes.RemoveAll(node => nodesToRemove.Contains(Int32.Parse(node.Id.ToString())));

And that’s it!

Drawbacks

My only reservation with this approach is that it may take some explaining to content editors. Restricting access to all other areas of Umbraco is controlled via the Members section. So this muddies the waters in terms of which areas of the back office perform which function. However I was unable to find another way. If anyone has any pointers to improve this, then please comment below.

MVC Controller Action called multiple times per page load.

Today I came across a real time sink of a gotcha with my MVC site. So I thought I’d share this on a post.

The issue
A standard Mvc controller action was getting called multiple times each time a GET request was made in the browser. Sometimes twice, sometimes once, and occasionally more.

Opening up Chrome dev tools to try and understand the problem, I spotted the multiple requests in the Network tab. Interstingly when I filtered by type the additional requests were showing up in the Images tab. Looking at the media type, the requests were being made the following acceptt header

image/webp,image/*,*/*

The initiator column pointed to javascript being the culprit.

chrome-dev-tools

However, this was a red herring. After a lot of code removal, view in browser, rinsing and repeating, I isolated it to the following line

<div id="hero" style="background-image: url('@Model')">

The cause
It took a while for the penny to drop, but the problem occurred when the Model was an empty string. So with nothing passed to the url property, the background image was requesting controller once the markup rendered. This was happening in a number of places in the page. So the controller was being called as many as four times each time the page was loaded.

I only tested this in Chrome, so the behaviour may not be the same across a other browsers.

The solution
As this markup was in a number of places across the site, I created an Html Helper extension, which simply checks to see if the string is null or empty.

My markup now looks like this

<section class="pageHero compactHero bottomAligned" @Html.StyleAttributeForHeroImage(Model.BackgroundImage)>

And the Helper looks like this;

public static class StyleAttributeExtensions
{
    public static MvcHtmlString StyleAttributeForHeroImage(this HtmlHelper helper, string imageUrl)
    {
        var styleAttribute = String.Format("style=\"background-image: url('{0}')\"", imageUrl);

        if (String.IsNullOrWhiteSpace(imageUrl))
        {
            styleAttribute = String.Empty;
        }
        return MvcHtmlString.Create(styleAttribute);

    }
}

So now I only have one request per controller action when each pages loads. This has resulted in a noticeable improvement in page load times.

Hope this helps someone else out.