Early Adopter's guide to Umbraco v14 [Trees] - Backend c# code
Published: 2024-02-21 12:28:17

So lets start with the c# code, mainly because its hopefully familar it can be setup once and then we can start tweaking the front end until it all works. 

> _Code for these posts is avalible in our [TimeDashboard Repository](https://github.com/KevinJump/TimeDashboard/) [FrontEnd (typescript)](https://github.com/KevinJump/TimeDashboard/tree/master/TimeDashboard.Client/assets/src/tree) & [Backend (c#)](https://github.com/KevinJump/TimeDashboard/tree/master/TimeDashboard.Client/Controllers/Time)_

## The Tree controller base. 
As we are following the Umbraco.Core patterns and splitting out controller methods into their own classes we will have a base controller.

```cs
[ApiController]
[BackOfficeRoute($"time/api/v{{version:apiVersion}}/tree")]
[ApiExplorerSettings(GroupName = "Time")]
[Authorize(Policy = "New" + AuthorizationPolicies.BackOfficeAccess)]
[MapToApi("time")]
public class TimeTreeControllerBase : Controller
{
    public TimeTreeControllerBase()
    { }

    protected PagedViewModel<TItem> PagedViewModel<TItem>(IEnumerable<TItem> items, long totalItems )
        => new () { Items = items, Total = totalItems };

}
```

This controller is nearly empty, we've added a `PagedViewModel` method that all the other controllers can share, so returning the results is eaiser. 

## Root Controller.
First thing with any tree is the root node (the top level one). This will often return slightly diffrent information than the nodes underneath, as it won't be an actuall item from your system/database but the placeholder for all the children. 

```cs
[ApiVersion("1.0")]
public class TimeTreeRootController : TimeTreeControllerBase
{
    public TimeTreeRootController() : base()
    { }

    [HttpGet("root")]
    [MapToApiVersion("1.0")]
    [ProducesResponseType(typeof(PagedViewModel<TimeTreeItemResponseModel>), StatusCodes.Status200OK)]
    public async Task<ActionResult<PagedViewModel<TimeTreeItemResponseModel>>> GetRoot(int skip =0, int take =100 )
    {
        var items = GetTreeItems();
        var result = PagedViewModel(items, items.Count());

        return base.Ok(result);
    }

    private static string[] _cultures = [
        "en-US", 
        "fr-fr",
        "en-GB"
    ];

    /// <summary>
    ///  dummy method to get us some tree items. 
    /// </summary>
    /// <returns></returns>
    private IEnumerable<TimeTreeItemResponseModel> GetTreeItems()
    {
        foreach(var culture in _cultures)
        {
            var cultureInfo = CultureInfo.GetCultureInfo(culture);

            yield return new TimeTreeItemResponseModel
            {
                Id = cultureInfo.Name.ToGuid(),
                HasChildren = false,
                Name = cultureInfo.Name,
            };
        }
    }
}
```

Here we have greatly simplified getting the tree items, we are returning a list of culture codes. from an array. you would almost certainly be going off to a database here to get your items. 

## The Children controller 
If the user does click on each of the children then umbraco will attempt to fetch any child items, _(for us we have none - but we do need to see how this works)_

```cs
[ApiVersion("1.0")]
public class TimeTreeChildrenController : TimeTreeControllerBase
{
    public TimeTreeChildrenController() : base()
    { }

    [HttpGet]
    [MapToApiVersion("1.0")]
    [ProducesResponseType(typeof(PagedViewModel<TimeTreeItemResponseModel>), StatusCodes.Status200OK)]
    public async Task<ActionResult<PagedViewModel<TimeTreeItemResponseModel>>> GetChildren(Guid parentId, int skip = 0, int take = 100)
    {
        var items = GetChildrenForParent(parentId);

        return Ok(PagedViewModel(items, items.Count()));
    }


    private IEnumerable<TimeTreeItemResponseModel> GetChildrenForParent(Guid? parentId)
    {
        yield return new TimeTreeItemResponseModel
        {
            Id = Guid.NewGuid(),
            HasChildren = false,
            Name = "Child item",
            Parent = parentId.HasValue 
                ? new ReferenceByIdModel
                {
                    Id = parentId.Value,
                } 
                : null
        };
    }
}
```

The child controller for us is even smaller, we are returning a fixed 'child item' because we are not doing any lookup. if the item has no children then setting hasChildren = false in the root would stop this call from happening, but returning an empty enumarable set would also work. 

## Models
A quick word on models, as you will see above we are returning a list of `TreeItemResponseModel` with these controller methods. that class contains some fields that Umbraco will expect when we move to the front end.

```cs
public class TimeTreeItemResponseModel  
{
    public Guid Id { get; set; }

    public ReferenceByIdModel? Parent { get; set; }

    public string Name { get; set; } = string.Empty;

    public string Type { get; set; } = string.Empty;


    public bool HasChildren { get; set; }
}
```

You can in theory inhert from `EntityTreeItemResponseModel` and you will get most of these fields, but in practice i found that didn't work for me, it might have been my generated API, or just other things, so for simplity i moved the fields to my single model.

### Swagger.
After adding this to my project I have some tree methods in swagger.

![Tree methods](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hlur1semujln8dvknpgv.png)

## API Generation.
We can then generate the client API resources via the `openapi-typescript-codegen' command.

```bash
openapi --input http://oursite/swagger/url/here --output src/api --useOptions --postfixServices Resource"
```
(we have this [baked into our repositories package.json](https://github.com/KevinJump/TimeDashboard/blob/master/TimeDashboard.Client/assets/package.json#L11) file for easy of use.)