# Handlebars.Net: Fun with [Flags]

**Date:** 2022-09-18  
**Author:** Kees C. Bakker  
**Categories:** .NET / C#  
**Tags:** Handlebars  
**Original:** https://keestalkstech.com/handlebars-net-fun-with-flags/

![Handlebars.Net: Fun with [Flags]](https://keestalkstech.com/wp-content/uploads/2022/09/who-s-denilo-cvchSJpMBk4-unsplash-1.jpg)

---

When working on the a project, the need arose for better handling of enums decorated with a `[Flags]` attribute. In a previous article we explored how to [use Handlebars.NET to generate JSON strings](https://keestalkstech.com/2022/09/handlebars-net-json-templates/). In this article we'll build further on that JSON generator to add support for enums. We will also move away from the static `JsonHandlebarsDotNet` to an injectable version.

Through this article I'll be working with the following enum:

```cs
[Flags]
enum MenuItems
{
    None = 0,
    Pizza = 1,
    Fries = 2,
    Pancakes = 4,
    Meatballs = 8,
    Pasta = 16,
    StuffWithP = Pizza | Pancakes | Pasta,
    All = Pizza | Fries | Pancakes | Meatballs | Pasta | StuffWithP
};
```

You can see that it has all the nice features a *flagged* enum comes with: multiple combinations and a *None* value. It is going to be fun; fun with flags!

![](https://keestalkstech.com/wp-content/uploads/2022/09/1455646400872.jpg)
*I'm not the only one that enjoys flags. Source: Big Bang Theory, Warner Bros. Television*

## Deconstructing a [Flags] enum

When we want to deconstruct our enum to the unique flag values, we see some special use cases:

- The `None` has value `0`, so it can't be used with the `HasFlag` operation, as it will always return `true`. So `MenuItems.Pasta.HasFlag(MenuItems.None)` will return true. We will need to skip this value.
- The `StuffWithP` and `All` values are combined values, so I want their individual values to appear in a deconstructed array.

The following method will deconstruct a *flagged* enum to a string array of unique values:

```cs
public static string[] DeconstructFlags(Enum items)
{
    if (items.GetType().GetCustomAttribute<FlagsAttribute>() == null)
    {
        throw new ArgumentException("Enum has no [Flags] attribute.", nameof(items));
    }

    // no value, no list
    var itemsValue = (int)(object)items;
    if (itemsValue == 0) return Array.Empty<string>();

    var result = new List<string>();

    foreach (var item in Enum.GetValues(items.GetType()))
    {
        if(item == null) continue;

        var value = (int)item;

        // skip combined flags
        if (!BitOperations.IsPow2(value))
        {
            continue;
        }

        if (items.HasFlag((Enum)item))
        {
            result.Add(item.ToString() ?? "");
        }
    }

    return result.ToArray();

}
```

This will return the following arrays:

```cs
void Echo(MenuItems items)
{
    Console.WriteLine($"{items}: {String.Join(" + ", DeconstructFlags(items))}");
}

Echo(MenuItems.None);
// outputs: None: None

Echo(MenuItems.Pasta);
// outputs: Pasta: Pasta

Echo(MenuItems.Fries | MenuItems.Pizza);
// outputs: Pizza, Fries: Pizza + Fries

Echo(MenuItems.StuffWithP);
// outputs: StuffWithP: Pizza + Pancakes + Pasta
```

*Note how it is skipping the `StuffWithP` value and deconstructs properly.*

## Make Handlebars.NET respect the [Flags]

Now, we would love to iterate over our *flagged* enums using an `{{#each }}` statement. By default this is not possible and Handlebars.NET will just return the integer value of your enum. We'll make a `FlaggedEnumObjectDescriptorProvider` that fixes this:

```cs
var order = new
{
    items = MenuItems.Pizza | MenuItems.Pancakes
};

string source = @"
{ 
    ""order"": [
        {{#each items}}""{{this}}""{{#unless @last}},
        {{/unless}}{{/each}}
    ],
    ""orders"": ""{{items}}""
}";

var hbs = new JsonTemplateGenerator().Handlebars;
hbs.Configuration.ObjectDescriptorProviders.Add(new FlaggedEnumObjectDescriptorProvider());

var template = hbs.Compile(source);
var json = template(order);
Console.WriteLine(json);
```

This will output:

```json
{
    "order": [
        "Pizza",
        "Pancakes"
    ],
    "orders": "Pizza, Pancakes"
}
```

The trick is to register a `FlaggedEnumObjectDescriptorProvider` that will handle the *flagged* enums.

### Implementing an IObjectDescriptorProvider

I did some research on how to implement an `IObjectDescriptorProvider` and took the [Handlebars.Net.Extension.Json array iterator](https://github.com/Handlebars-Net/Handlebars.Net.Extension.Json/blob/main/source/Handlebars.Extension/JsonElementIterator.cs#L107-L159) as inspiration. The class checks if the `type` is an enum with the `[Flags]` attribute and returns an iterator that uses the `DeconstructFlags` method we've seen before.

```cs
using HandlebarsDotNet.Compiler;
using HandlebarsDotNet.Iterators;
using HandlebarsDotNet.ObjectDescriptors;
using HandlebarsDotNet.PathStructure;
using HandlebarsDotNet.Runtime;
using HandlebarsDotNet.ValueProviders;
using System.Numerics;
using System.Reflection;

public class FlaggedEnumObjectDescriptorProvider : IObjectDescriptorProvider
{
    public bool TryGetDescriptor(Type type, out ObjectDescriptor value)
    {
        if (!type.IsEnum || type.GetCustomAttribute<FlagsAttribute>() == null)
        {
            value = ObjectDescriptor.Empty;
            return false;
        }

        value = new ObjectDescriptor(
            type,
            null,
            null,
            self => new FlagEnumInterator()
        );

        return true;
    }

    public static string[] DeconstructFlags(Enum items)
    {
        if (items.GetType().GetCustomAttribute<FlagsAttribute>() == null)
        {
            throw new ArgumentException("Enum has no [Flags] attribute.", nameof(items));
        }

        // no value, no list
        var itemsValue = (int)(object)items;
        if (itemsValue == 0) return Array.Empty<string>();

        var result = new List<string>();

        foreach (var item in Enum.GetValues(items.GetType()))
        {
            if(item == null) continue;

            var value = (int)item;

            // skip combined flags
            if (!BitOperations.IsPow2(value))
            {
                continue;
            }

            if (items.HasFlag((Enum)item))
            {
                result.Add(item.ToString() ?? "");
            }
        }

        return result.ToArray();

    }

    internal class FlagEnumInterator : IIterator
    {
        public void Iterate(in HandlebarsDotNet.EncodedTextWriter writer, HandlebarsDotNet.BindingContext context, ChainSegment[] blockParamsVariables, object input, TemplateDelegate template, TemplateDelegate ifEmpty)
        {
            using var innerContext = context.CreateFrame();
            var iterator = new IteratorValues(innerContext);
            var blockParamsValues = new BlockParamsValues(innerContext, blockParamsVariables);

            blockParamsValues.CreateProperty(0, out var _0);
            blockParamsValues.CreateProperty(1, out var _1);

            iterator.First = BoxedValues.True;
            iterator.Last = BoxedValues.False;

            var target = DeconstructFlags((Enum)input);

            var count = target.Length;
            var enumerator = target.GetEnumerator();

            var index = 0;
            var lastIndex = count - 1;
            while (enumerator.MoveNext())
            {
                var value = enumerator.Current;
                var objectIndex = BoxedValues.Int(index);

                if (index == 1) iterator.First = BoxedValues.False;
                if (index == lastIndex) iterator.Last = BoxedValues.True;

                iterator.Index = objectIndex;

                object resolvedValue = value;

                blockParamsValues[_0] = resolvedValue;
                blockParamsValues[_1] = objectIndex;

                iterator.Value = resolvedValue;
                innerContext.Value = resolvedValue;

                template(writer, innerContext);

                ++index;
            }

            if (index == 0)
            {
                innerContext.Value = context.Value;
                ifEmpty(writer, innerContext);
            }

        }
    }
}
```

*Notice how `@first` and `@last` are also implemented by this class.*

### Improve the JsonTemplateGenerator

We can now add the `FlaggedEnumObjectDescriptorProvider` to the `ObjectDescriptorProviders` of our `JsonTemplateGenerator` constructor:

```cs
public JsonTemplateGenerator()
{
    Handlebars = HandlebarsDotNet.Handlebars.Create();
    Handlebars.Configuration.TextEncoder = new JsonTextEncoder();
    Handlebars.Configuration.ObjectDescriptorProviders.Add(new FlaggedEnumObjectDescriptorProvider());
}
```

One might also consider adding the [helpers](https://github.com/Handlebars-Net/Handlebars.Net.Helpers) to it: `HandlebarsHelpers.Register(Handlebars);`

## Final thoughts

I just love Handlebars and the fact that the template engine is so versatile. It is super easy to handle enum flags.

## Further reading

When writing this article, I stumbled upon some resources that might interest you:

- [StackOverflow answer: What does the [Flags] Enum Attribute mean in C#?](https://stackoverflow.com/a/8480) - I didn't know that it did nothing with values, but only with the `.ToString()`.
- [StackOverflow question: Why are flag enums usually defined with hexadecimal values](https://stackoverflow.com/q/13222671) - I personally don't define them that way, but this article has some nice insights to how you can define them. I like the `1 ` notation, suggested by the answer.

## Changelog

- 2022-09-24: Moved the dependency injection section to [Handlebars.Net & JSON templates: Inject a JSON Template Generator](https://keestalkstech.com/2022/09/handlebars-net-json-templates/#inject-a-json-template-generator).
- 2022-09-18: Initial article.
