Your Blazor component needs data in a certain format, where to Map?

Published on

Blazor components can accept parameters of virtually any type.

This makes for a simple option when you fetch data from a backend API/service as you can often use that data “as is” in your component.

For example, you might call an API to fetch product details which returns a list of items of type Product. The chances are you can just use that directly in your Blazor UI.

@foreach(var product in products) {
<ProductDetails Product="@product"/>
}

In this example the ProductDetails component is specific to this page, and tightly coupled to the structure of the incoming Product data, so this feels like a sound approach.

Another option is to have your component accept primitives like string, or int, and send those in to the component.

<ProductDetails name="@product.Name" price="@product.Price" />

Now we could use ProductDetails anywhere we like, so long as we give it values for its Name and Price.

But, what if we have a truly re-usable component that needs to accept data in a certain format.

For example, let’s say we have a TreeView component which takes in a list of TreeItem objects.

TreeView.razor

[Parameter]
public List<TreeItem> Items { get; set; }

TreeItem.cs

public class TreeItem {
public string Name { get; set; }
public List<TreeItem> Children { get; set; }
}

You can imagine this could be useful for showing nav items, or a folder tree, or anything else with a ‘Tree Like’ structure.

But now we’ve got a problem!

Every time we use this TreeView component, we need to map the data we’re trying to use (often from a backend API or service) to a list of TreeItem.

For example, we might pull a list of folders from a server and want to render those as a Tree.

FolderView.razor

<TreeView Items="?"/>
@code {
protected override void OnInitialized() {
var folders = _backendAPI.ListFolders();
// map folders to items of type `TreeItem`
}
}

So where do we put the mapping code?

Well, one option in this case is to write the mapping code directly in the component (FolderView in this case):

FolderView.razor

<TreeView Items="treeItems"/>
@code {
List<TreeItem> treeItems = new();
protected override void OnInitialized() {
var folders = _backendAPI.ListFolders();
foreach(var folder in folders){
treeItems.Add(new TreeItem { Name= folder.Name });
// possibly map sub folders here as well
}
}
}

This is probably fine if the mapping code is simple/minimal (as above).

But if you’re mapping between objects with more properties, and perhaps a bit more logic to ensure the correct data comes out the other side, this can turn into a lot of messy code.

So where else could it go?

Enter Extension Methods - An Alternative

This is where I quite often turn to extension methods.

You can create a separate method (in a separate class, in a separate file) which has the sole job of mapping data from your backend type to the structure needed for your component.

public static class FolderExtensionMethods
{
public static IEnumerable<TreeItem> ToTreeItems(this List<Api.Folder> folders)
{
List<TreeItem> treeItems = new List<TreeItem>();
foreach (var folder in folders)
{
treeItems.Add(new TreeItem { Name = folder.Name });
// possibly map sub folders here as well
}
return treeItems;
}
}

Or, for bonus points, you can use yield return here for a more succinct version 🎯

public static class FolderExtensionMethods
{
public static IEnumerable<TreeItem> ToTreeItems(this List<Api.Folder> folders)
{
foreach (var folder in folders)
{
// additional mapping goes here
yield return new TreeItem { Name = folder.Name };
}
}
}

Either way, you can use this extension method in your component, and your data is successfully mapped!

FolderView.razor

@using FolderExtensionMethods;
<TreeView Items="treeItems"/>
@code {
IEnumerable<TreeItem> treeItems;
protected override void OnInitialized()
{
var folders = _backendAPI.ListFolders();
treeItems = _folders.ToTreeItems();
}
}

Your component remains easy to read, understand and maintain, plus you’ve encapsulated all that unsightly mapping code into a separate method which you can invoke at will.

Struggling to figure out what to focus on with Blazor?

BlazorSharp - The .NET Web Developers community is here to help!

  • Connect with your fellow .NET web developers
  • Keep up to date with the latest .NET changes
  • Exchange tips, tools and tactics
Join Now
Next Up
  1. Are you sure you need that 'else'
  2. Blazor Changes in .NET 8 - Solving Blazor's biggest challenges?
  3. Use a recursive Blazor component to render a TreeView