Early Adopter's guide to Umbraco v14 [Trees] - Backend c# code
Kevin Jump
Posted on February 21, 2024
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 };
}
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,
};
}
}
}
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
};
}
}
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; }
}
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.
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.)
Posted on February 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.