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 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
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.
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.
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 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:
public class NullableDecimalConverter : IPropertyValueConverter
{
readonly IPropertyValueConverter coreConverter = new DecimalValueConverter();
...
You can see the full example of the class in my package's repo.
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:
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:
- A property has been added to a content type, but some content of that type has not been (re)published since.
- Content has been created via the API, without the property value having been set.
In these cases the following converters might be useful:
-
YesNoDefaultConverter
which returns the "Initial State" (default) value that has been configured in the property editor's settings. -
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 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.
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.
Top comments (1)
Hi Jason,
Sorry this is a little off-topic.
I was watching your excellent YouTube video on RCL in Umbraco 10 but I cannot, no matter how hard I try to get the razor views inside an RCL to recompile on save. I see the browser refresh, or if I run "dotnet watch run" I see the file change prompt. But nothing happens! I've tried it on a completely new install using VS2022. Do you have a github repo of the demo you showed on YouTube?