Shared models - Blazor's (not so) secret super power
November 26, 2019 · 5 minute read · Tags: blazor
If you’ve built applications using javascript and some form of Web API, you’ve probably run into the “separate models” problem.
This is where you define one model in the backend (in .NET, typically a C# class) and return instances of it via your API (serialised to JSON to send over the network).
public class CartLineItem
{
public int Id { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public decimal Price { get; set; }
}
So far, so simple.
Now you want to retrieve that data and use it in your javascript application.
So you make your network call, retrieve the model as JSON and whack it into a javascript (or indeed Typescript) object…
const cartLineItem = await httpGet('/api/cart/line/1');
This example uses a little httpGet helper, but essentially takes the JSON and deserializes it into a javascript object.
If you’re using Typescript, you might even specify the structure of that object (so you get handy intellisense and compile errors if you try to access a property that doesn’t exist).
interface ICartLineItem {
Id: number;
Title: string;
Image: string;
Price: number;
}
Now at this point, everything works and all is well with the world.
But, you may have noticed web applications have this nasty habit of changing and this architecture makes changes a little difficult.
We’ve ended up with two versions of our model, separated by that funny thing called the internet, sitting between your browser and your API and this is where things get a bit… messy.
What if your backend model changes?
Say you decide Image is a terrible property name and want to change it to ImagePath; if you’re feeling a bit cavalier you might just go ahead, say to hell with the consequences and use the refactoring tools in your IDE to change it there and then (after all, I’m all for improving the readability of code)…
But what now for your poor javascript application?
It doesn’t know you’ve improved your model; all it knows is it suddenly can’t find anything called Image on your object.
The result? A sea of errors in your browser console…
A simple change has turned into some kind of crazy game of whack-a-mole; bugs are popping up all over the place and the only way to fix them is to hunt each one down and update the reference to Image.
How does Blazor handle it?
Blazor has an obvious answer to this problem of keeping two models in sync; don’t use two models!
Remember that CartLineItem class we mentioned earlier?
In Blazor, you would populate that model with data (presumably from a database or some-such) but then, when it comes to using the model in your components, you can reference CartLineItem
directly.
Here’s an example with Blazor Server:
<h3>Your Shopping Cart</h3>
@foreach (var line in Lines)
{
<Item details="@line"/>
}
@code {
public List<CartLineItem> Lines;
protected override void OnInitialized()
{
Lines = _cartService.ForLoggedInCustomer();
}
}
We can retrieve a list of lines using _cartService
then assign those to a field. In our markup we can loop over that list of lines and bind each one to whatever we like (a component called Item in this case).
Blazor WebAssembly manages a similar trick even though it’s using network calls to retrieve the data:
@inject HttpClient Http
<h3>Your Shopping Cart</h3>
@foreach (var line in Lines)
{
<Item details="@line"/>
}
@code {
public CartLineItem[] Lines;
protected override async Task OnInitializedAsync()
{
Lines = await Http.GetJsonAsync<CartLineItem[]>("api/cart");
}
}
This will take the JSON returned via the network call and cast it to an array of CartLineItem
.
In either case, if you decide to rename the Image property on CartLineItem
now, the compiler will catch any code which is still using the old property name and your application won’t even compile.
Plus, you can use your IDE’s refactoring tools to rename Image and automatically fix all references to it.
Give it a go for yourself; spin up a new Blazor project and you’ll find a class called WeatherForecast
. Try just renaming one of the properties and then try to compile your application.
You’ll see an error like this:
‘WeatherForecast’ does not contain a definition for ImagePath’
In Visual Studio you can double-click the error and it will take you straight to the place you need to change:
Fix the red squiggle and you’re done!