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.
