Best practice tips for large scale Umbraco websites

Experience based tips for building large and complex websites in Umbraco

Umbraco is a great CMS, it simple, easily extendible and most important it is free.

However, it is not build for large scale websites, since presentation and back-end are on the same machine and all published content is stored in AppData/umbraco.config file. This is good because it can be easily cached and database read is reduced. How ever, if your content is large (let's say you want to host 10 websites in the same Umbraco instance) this file will also grow.

In such cases you need to take care about every small thing so your pool does not get clogged and eventually restart which makes your website unaccessible for some time.

The following are some tips and trick I learned while working on some projects of this type where one Umbraco instance is used for hosting several large websites.

1. Extension methods

To avoid exceptions, you need to handle a bunch of possible cases that might happen during content populating from content managers. Let's face it, most of the time they know nothing or just a little about how web works. This means you have to be prepared for most of the illogical things when content is entered.

This requires a lot of checking when reading properties, that means a lot of conditions. These conditions usually repeat, so use extension methods for fetching values from properties.

public static string GetPropertyValueAsString(this IPublishedContent page, string propertyName)
{
    string result = string.Empty;
    if (page.HasProperty(propertyName) && page.GetPropertyValue(propertyName) != null)
    {
		result = page.GetPropertyValue(propertyName).ToString();
    }
    return result;
}
    

This simple extension ensures you will always get string value from property, so no need to check dozen of times whether value is null or not. More extension methods you can find in this article or fork the class library project at GitHub with some extension I wrote.

Using extension methods will significantly reduce the size of code in you Razor views and make website more maintainable.

2. Be careful with LINQ

If you access one node, let's say you locate website root with AncestorOrSelf(1) call, store it in some variable, because you will probably need it again.

Do not re-invoke LINQ queries if you already have a result acquired before.

3. Use Umbraco relations

If you have few websites hosted inside one instance of Umbraco you probably want to keep some among between pages in different websites. Usually these will be the same websites (for same company), just for different languages or regions or both. This means when switching from a page in English to Russian, you want to keep the client at the same page but in a different language.

This can be easily achievent with Umbraco relations. When you take one node in Umbraco to copy, you have an option at the bottom of dialog to keep relation to a source node. If you check this value you will copy node but Umbraco will remember from which node it was copied from.

It does that by saving id values in a single tabe in Umbraco databse. Fortunatelly there is an API which whaps this functionality so you do not need to go inside database and read raw data.

       public static IPublishedContent GetRelatedContent(this IPublishedContent Content, IPublishedContent TargetWebsite)
        {
            IPublishedContent related = null;

            UmbracoHelper helper = new UmbracoHelper(UmbracoContext.Current);
            var allRelations = umbraco.cms.businesslogic.relation.Relation.GetRelations(Content.Id);
            if (allRelations != null && allRelations.Count() > 0)
            {
                var relations = allRelations.Where(r => !r.Child.IsTrashed && !r.Parent.IsTrashed).ToArray();
                if (Content.AncestorOrSelf(1).Id != TargetWebsite.Id)
                {
                    foreach (var rel in relations)
                    {
                        var child = helper.TypedContent(rel.Child.Id);
                        if (child != null && child.AncestorOrSelf(1).Id == TargetWebsite.Id)
                        {
                            related = child;
                        }
                    }

                    if (related == null)
                    {
                        foreach (var rel in relations)
                        {
                            var parent = helper.TypedContent(rel.Parent.Id);
                            if (parent != null && parent.AncestorOrSelf(1).Id == TargetWebsite.Id)
                            {
                                related = parent;
                            }
                        }
                    }
                }
                else
                {
                    related = helper.TypedContent(relations.First().Parent.Id);
                }
            }
            if (related == null)
            {
                related = TargetWebsite;
            }
            return related;
        }
    

This code will get you a related node in a target website or return target website in case it does not find related content in target website (container node).

4. Expose shared images from one single domain

Since all websites in shared Umbraco instance will most likely use the same images, it is pointless to serve different image URL for every websites. Browsers and most of proxies cache content based on url, so that means if you host one image from en.mydomain.com/media/1234/image.jpg and ru.mydomain.com/media/1234/image.jpg they will most likely be cached as two separate images. 

This will cause additional bandwidth for your website. In case you have more websites, this will just multiply.

        public static string GetCroppedUrl(this IPublishedContent media, string cropName, bool useImageCDN = true)
        {
            if (media != null)
            {
                string result = media.Url;
                if (!string.IsNullOrWhiteSpace(Path.GetExtension(result)))
                {
                    result = Path.Combine(Path.GetDirectoryName(result),
                        string.Concat(Path.GetFileNameWithoutExtension(result), "_", cropName));
                    result = string.Concat(result, ".jpg");

                    if (useImageCDN && !string.IsNullOrWhiteSpace("//shared.mydomain.com"))
                    {
                        result = string.Concat(DynConfig.Item("//shared.mydomain.com"), result);
                    }
                    result = result.Replace("\\", "/");
                }
                return result;
            }
            return string.Empty;
        }
    

This extension method will give you image URL of the cropped image based on crop name, but it will use the same domain in address for each and every website you host in same Umbraco instance.

5. Do not store your logic in Razor views

Views are not intended to keep any logic, they are presentational level, so keep them that way. Avoid function declaration inside views because at some point after development is done, when you need to maintain the website you will have big problems finding where is what. Even more difficult to find "what poet wanted to say" will be for someone else who might have to maintain website.

6. Never set your croppers to 100% of quality

Setting your image cropper data type to 100% of image quality will basically produce a lot larger image than it should. At least set the quality to 95% or less.

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

.NET

read more

JavaScript

read more

SQL/T-SQL

read more

PowerShell

read more

Comments for this article