Early Adopter's guide to Umbraco v14 [Trees] - Backend c# code

kevinjump

Kevin Jump

Posted on February 21, 2024

Early Adopter's guide to Umbraco v14 [Trees] - Backend c# code

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 FrontEnd (typescript) & Backend (c#)

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.



[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 };

}


Enter fullscreen mode Exit fullscreen mode

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.



[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,
            };
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

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)



[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
        };
    }
}


Enter fullscreen mode Exit fullscreen mode

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.



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; }
}


Enter fullscreen mode Exit fullscreen mode

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

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 file for easy of use.)

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
kevinjump
Kevin Jump

Posted on February 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About