How to avoid NullReferenceExceptions while working with Sitecore

Today we’ll talk about our favorite exception in C# 🙂

The purpose of this article is to help rid the codebase of nulls and create somewhat of a “null-safe” domain where everything can operate smoothly without nulls creating object property access issues (e.g. item.Parent.Language.Name)

I work with Sitecore, so all of the following examples will use Sitecore objects. But this approach could be applied to all “nullable” objects (which are all reference types in C#).

Problem Description

I’ll show you what I mean.
Let’s say I need Home’s parent item’s display name.

Database db = Factory.GetDatabase("master");
Item item = db.GetItem("/sitecore/content/SiteRoot/Home");
var displayName = item.Parent.DisplayName;

Looks good, doesn’t it?
But hey.. what if your Sitecore tree doesn’t contain ‘Home’?

Does the type system tell you that you might not get your item? Or does the method name say something about this?
No and No!

And here is your first NullReferenceException on production 🙂

So usually you’ll start to amass these ugly null checks.. and then your code looks something like this:

Database db = Factory.GetDatabase("master");
if (db != null)
{
  Item item = db.GetItem("/sitecore/content/SiteRoot/Home");
  if (item!= null)
  {
    if (item.Parent != null)
    {
      // Oh, finally...
      var displayName = item.Parent.DisplayName;
    }
  }
}

Solution

Now that we see the problem clearly, we can come-up with a solution.

In the best-case scenario, the “type” that we’ll be operating with should explicitly say the object can be a null. And the type should force us to handle each null case separately!

This idea comes from the Functional Languages world, where it is really common.
The solution is to use monads.

Think of a monad as a generic type that encapsulates a nullable object and restricts direct access to its value.

This generic type is known as a “Maybe” type:

Maybe item;

This type should have at least two methods: one for converting a nullable object to a Maybe object and one for extracting the value back.

Maybe maybeItem = Maybe.Apply(db.GetItem("/sitecore/content/SiteRoot"));
....
var displayName = maybeItem.Return(
                    some: item =>; item.DisplayName,
                    none: () => string.Empty
                  );

The key here is that handlers should be set for two cases!

In addition, another ability can be added to the Maybe type: the ability to bind objects in a chain, creating a pipeline that will allow us to dive deep into an object’s structure and do some calculations!

Binding

We actually use the Bind method to create a chain for accessing a nested object’s property.
Here is what the basic code looks like:

Database db = Factory.GetDatabase("master");
var parentsDisplayName = db.GetItem("/sitecore/content/SiteRoot/Home")
                           .Getitem().Parent.DisplayName;

Here is what it looks like with the monad:

Maybe database = Maybe.Apply(Factory.GetDatabase("master"));

var parentsDisplayName  = database
  .Bind(db => db.GetItem("/sitecore/content/SiteRoot/Home"))  
  .Bind(item => item.Parent)  
  .Bind(parent => parent.DisplayName)
  .Return(
    some: displayName => displayName,
    none: () => string.Empty
  );

This already has a built-in null check, so it is no longer possible to get a NullReferenceException!

Extending functionality. If and Do.

Sometimes additional non-null related checks are required when going through the call chain.
In these situations, the monad functionality can be extended with If and Do statements.
For instance, compare this…

public string ExClassic()
{
  string parentsLangName = string.Empty;

  Database db = Factory.GetDatabase("master");

  if (db != null)
  {
    Item item = db.GetItem("/sitecore/content/SiteRoot");

    if (item.DisplayName != null)
    {
      // maybe we have to check some conditions
      if (item.ID != ID.Parse("{11111111-1111-1111-1111-111111111111}"))
      {
        if (item.Parent != null)
        {
          // can call some func - for ex Log that parent is not null
          Console.WriteLine(String.Format("Not null, {0}", item.Parent));

          if (item.Parent.Language != null)
          {
            if (item.Parent.Language.Name != null)
            {
              // Oh, finally...
              parentsLangName = item.Parent.Language.Name;
            }
          }
        }
      }
    }
  }
  return parentsLangName;
}

…to it’s monadic equivalent:

public string ExMaybeEquivalent()
{
  Maybe<Database> database = Maybe.Apply(Factory.GetDatabase("master"));

  string parentsLangName = database
    .Bind(db => db.GetItem("/sitecore/content/SiteRoot"))
    .If(item => item.ID != ID.Parse("{11111111-1111-1111-1111-111111111111}"))
    .Bind(item => item.Parent)
    .Do(parent => Console.WriteLine(String.Format("Not null, {0}", parent)))
    .Bind(parent => parent.Language)
    .Bind(lang => lang.Name)
    .Return(
      some: name => name,
      none: () => string.Empty
    );

  return parentsLangName;
}

Conclusion

Again, the main idea of this approach is to create a safe domain and provide this safety through the type system.

It is probably a good idea to create an abstraction over Sitecore’s objects and translate them in the new null-safe style.

It’s possible to create a GetItem() method that will return a Maybe type.

public static Maybe<Item> GetItem(string path);  

This Maybe type can then be used throughout the code.

There are a lot of C# monad implementations. I use my own simple implementation with a small set of methods that are needed in the Sitecore domain.

Maybe monad for Sitecore
Check this out, probably you’ll like this approach:)

Check this out. You will probably like this approach.

This entry was posted in C#, Sitecore and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s