Why I built an Umbraco package that does "nothing"
Tags: umbraco
Published: 2022-06-28 06:52:56

Well, maybe not _nothing_, but definitely null.

Consider this problem: You have a temperature property on a page, and that property is optional. How do you distinguish between no value, and 0°C in your code?

By default, a blank decimal or integer property in the Umbraco backoffice will come out the other side of the default [property value converter](https://our.umbraco.com/documentation/extending/property-editors/value-converters) with the default value of 0. This means to be able to differentiate between an empty value and 0 you need to do some extra work.

## How I used to do it

![Animation of adding nested content in the Umbraco backoffice containing just one Temperature property](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d86ee51o0cbcjgygqwv2.gif)

Nested content. It works out-of-the-box and is very intentional. The property inside the nested content needs to be required and the editor can choose to "add" a temperature value, or leave it empty.

But it's not pretty, and it's a rather clunky flow for editors.

## The backoffice already knows the difference

The backoffice already distinguishes between an empty value and a 0 in a decimal/integer property. In this screenshot Temperature is "0", but Temperature 2 is empty.
![Two Umbraco decimal properties, one is empty, the other is zero.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ls41p98cs1ex0ey1o7us.png)

So all we need is a nice way of differentiating between empty and 0 values in our code.

## Make those values `nullable`!

This is a good place to make use of nullable value types, because these _are_ nullable values! Umbraco even already stores them as NULL in the database. So instead of our decimal property returning a `decimal` value type, we can make it return a `decimal?` type.

### Do it yourself

To do this we'll need to use a new property value converter that can return a null.

Inherit from (or use the decorator pattern) to make a new class that replaces the `ConvertSourceToIntermedia` Method of the `Umbraco.Cms.Core.PropertyEditors.ValueConverters` class. Making it nullable is actually quite straightforward.

```csharp
public object? ConvertSourceToIntermediate(
    IPublishedElement owner,
    IPublishedPropertyType propertyType,
    object? source,
    bool preview)
{
    if (source is null)
    {
        return null;
    }

    // is it already a decimal?
    if (source is decimal)
    {
        return source;
    }

    // is it a double?
    if (source is double sourceDouble)
    {
        return Convert.ToDecimal(sourceDouble);
    }

    // is it a string?
    if (source is string sourceString && decimal.TryParse(sourceString, NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out decimal d))
    {
        return d;
    }

    // couldn't convert the source value - default to null
    return null;
}
```
My site makes use of [Nullable Reference Types](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references) so, as I am returning null, I need to change the return type from `object` to `object?`. This means I can't simply inherit, so my class is actually a decorator:

```csharp
public class NullableDecimalConverter : IPropertyValueConverter
{
    readonly IPropertyValueConverter coreConverter = new DecimalValueConverter();
...
```
You can see the full example of the class [in my package's repo](https://github.com/JasonElkin/Emptiness/blob/f64cd94ca202787cdf59efa5c873ac31dd4281ef/src/Our.Umbraco.Emptiness/PropertyValueConverters/NullableDecimalConverter.cs).

Because property value converters are type-scanned at startup, and you can only only have one property value converter for a type at a time, you will need to replace `DecimalValueConverter` with `NullableDecimalConverter` at startup.

## There's a package for that

As well as decimals, there are a number of other property types that return value types that should probably be able to return null values.

- Date Picker - default value is 0001-01-01 00:00:00
- Integer - default value is 0
- Labels

So I created a package called Emptiness:
[![Umbraco Emptiness](https://opengraph.githubassets.com/ed6480e15ef66fe593be0961294dab37edb193df61c51583fa279d271a52759e/JasonElkin/Emptiness)](https://github.com/JasonElkin/Emptiness)

It adds nullable property value converters for the property types mentioned above, as well as a bonus...

### Toggles (True/False)
Though toggles don't have a null state in the UI, they can be empty if:

1. A property has been added to a content type, but some content of that type has not been (re)published since. 
2. Content has been created via the API, without the property value having been set.

In these cases the following converters might be useful:

1. `YesNoDefaultConverter` which returns the "Initial State" (default) value that has been configured in the property editor's settings.
2. `NullableYesNoConverter`, returns null if content hasn't yet been (re)published with the property.

## Getting started

Just install it from NuGet and go. 

```
> dotnet add package Our.Umbraco.Emptiness
```

There's a [default configuration](https://github.com/JasonElkin/Emptiness#configuration) that I think will make sense for most sites. It makes empty integer, decimal and date pickers null when empty and makes Toggle's return their default value.

After installing & configuring rebuild your ModelsBuilder models and you'll see the relevant properties have been made nullable (so you'd better null-check them 😉).

## Feedback please 🙏

The package is still pretty new, and I'm only using it in anger on one site, so I really welcome any [feedback/issues etc.](https://github.com/JasonElkin/Emptiness/issues)

It's somewhat different to what we're used to when working with properties in Umbraco, but I've found nullable values are a much better way of taking what's happening in the backoffice editor UI and surfacing that in our website code.