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

April 3, 2023 · 4 minute read · Tags: blazor

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.

Web development should be fun.

Write code, hit F5, view it in the browser and bask in the glory of a job well done.

But you're not basking… Why aren't you basking?!

Cut through all the noise and build better, simpler Blazor web applications with Practical Blazor Components.

Practical Blazor Components Build Better Blazor Web Apps, Faster

Next up

How to upload a file with Blazor SSR in .NET 8?
How to handle file uploads without using an interactive render mode?
3 simple design tips to improve your Web UI
Spruce up your features
The quickest way to integrate PayPal checkout with Blazor SSR in .NET 8
JavaScript Interop works differently with Blazor Server-side rendering