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!

Unleash Blazor's Potential

Blazor promises to make it much easier (and faster) to build modern, responsive web applications, using the tools you already know and understand.

Subscribe to my Practical ASP.NET Blazor newsletter and get instant access to the vault.

In there you'll find step-by-step tutorials, source code and videos to help you get up and running using Blazor's component model.

I respect your email privacy. Unsubscribe with one click.

    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!

    Unleash Blazor's Potential

    Blazor promises to make it much easier (and faster) to build modern, responsive web applications, using the tools you already know and understand.

    Subscribe to my Practical ASP.NET Blazor newsletter and get instant access to the vault.

    In there you'll find step-by-step tutorials, source code and videos to help you get up and running using Blazor's component model.

    I respect your email privacy. Unsubscribe with one click.

      Next up

      Finally! Improved Blazor Server reconnection UX
      .NET 9 changes how your Blazor Server app behaves when server connection is lost
      .NET 9 improves JavaScript module importing for Blazor
      .NET 9 ensures your users always get the latest version of your JS modules
      How to use .NET 9 to ensure users always get the latest version of your stylesheets
      .NET 9 changes how static files are served, and it solves a long-standing problem