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.
Build Better Blazor Web Apps, Faster