Anton ☕

First Impressions: Blazor WebAssembly

February 20, 2020

I have known about Blazor since it was still a proof-of-concept from a couple of years back. It showed a compelling option especially if you are an ASP.NET developer wanting to build rich UI but is hesitant to dive deep into JavaScript.

Now that .NET Core 3.1 has been released, I decided to give Blazor a try to see what the fuss is all about.

I have been, primarily, an ASP.NET developer for much of my career since 2003, but have recently (around 2015) been reinventing myself as a frontend web developer (HTML, CSS, JS), mainly using modern JavaScript and React as my framework of choice. So I was curious about the difference in experience between Blazor and React.

As an experiment, I decided to convert the tutorial from React’s website into Blazor.

You can find the repo here: https://github.com/atamocius/blazor-tictactoe and to see it running, go here: https://blazor-tictactoe.netlify.com/

The original React version is here: https://codepen.io/gaearon/pen/gWWZgR?editors=0010

Seeing that Blazor WebAssembly is still in preview at the time of writing, I won’t be commenting on the debugging experience as it is still being worked on. I plan to update this post once it goes out of preview.

WebAssembly vs. Server

From the start, Blazor was being marketed as “.NET for the web client, enabled by WebAssembly”. Now it has 2 flavors: Blazor WebAssembly and Blazor Server. I chose the WebAssembly version for this experiement since, I think, this provides the most promise (ie. writing .NET web apps without a server).

Tic-Tac-Toe Tutorial Comparison

Below are side-by-side comparisons between the React and Blazor versions.

Square component

/// REACT

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}
/// BLAZOR

<button class="square" @onclick="OnClick">
    @Value
</button>

@code {
    [Parameter]
    public string Value { get; set; }

    [Parameter]
    public Action OnClick { get; set; }
}

In the React version, we can see that JSX is working in tandem with JS and acts as a language extension; working as expressions that can be interspersed within the JS logic.

In contrast, the Blazor version uses Razor as a template and C# as behavior on top of the template. The markup uses the @ symbol to denote behavioral annotations that map to the C# code.

As we’ve seen throughout the years, both approaches have their pros and cons. With React, you can write more concise code that inlines well with the logic, while Blazor’s separate view and logic approach is easier to reason with especially when switching thought between design and logic.

Board component

/// REACT

class Board extends React.Component {
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
      />
    );
  }

  render() {
    return (
      <div>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}
/// BLAZOR

@{
    Func<int, object> renderSquare = i =>
    {
        <Square Value="@Squares[i].ToString()" OnClick="() => OnClick.InvokeAsync(i)"/>;

        return null;
    };
}

<div>
    <div class="board-row">
        @renderSquare(0)
        @renderSquare(1)
        @renderSquare(2)
    </div>
    <div class="board-row">
        @renderSquare(3)
        @renderSquare(4)
        @renderSquare(5)
    </div>
    <div class="board-row">
        @renderSquare(6)
        @renderSquare(7)
        @renderSquare(8)
    </div>
</div>

@code {
    [Parameter]
    public IReadOnlyList<char> Squares { get; set; }

    [Parameter]
    public EventCallback<int> OnClick { get; set; }
}

As we’ve seen previously, React allows us to control the flow of the render directly using logic.

Albeit not well documented, Blazor allows for the same type of writing. In the case of the Board component, I was able to create a lamba function that has markup in its body (renderSquare). There are some limitations though. That same lambda function cannot reside in the @code block because the code block only supports C#. The lambda within a Razor block (@{}) works since it is considered a local member for the markup.

The semicolon after the markup in the lambda function is optional. I placed it there because Visual Studio Code was having issues coloring the syntax.

I say “not well documented” because, at the time of writing, I wasn’t able to find official documentation as to why the lambda function needs to return an object. There could be documentation, but certainly not filed under Blazor.

Certainly I could have written the Blazor version idiomatically (ie. @for, @foreach, @if, etc.), but my goal is to see how close I could get it to look like React’s syntax. Since the Blazor team is touting this as an alternative to JS based frameworks, what better way to convince people of that than to show as little difference as possible.

Game component

There is quite a bit to unpack here, so I’ll only focus on certain parts.

State Initialization

/// REACT

constructor(props) {
  super(props);
  this.state = {
    history: [
      {
        squares: Array(9).fill(null)
      }
    ],
    stepNumber: 0,
    xIsNext: true
  };
}
/// BLAZOR

private IReadOnlyList<Frame> history;
private int stepNumber;
private bool xIsNext;

protected override void OnInitialized()
{
    this.history = new []
    {
        new Frame(),
    };
    this.stepNumber = 0;
    this.xIsNext = true;
}

In Blazor, a lifecycle method called OnInitialized is used to initialize state. State in this case is not necessarily an object but just member variables. Just like in React, there are lifecycle methods available to control logic during mounting and unmounting of the component.

The Frame class is just a wrapper for the array of values that represent the board.

Rendering

/// REACT

render() {
  const history = this.state.history;
  const current = history[this.state.stepNumber];
  const winner = calculateWinner(current.squares);

  const moves = history.map((step, move) => {
    const desc = move ?
      'Go to move #' + move :
      'Go to game start';
    return (
      <li key={move}>
        <button onClick={() => this.jumpTo(move)}>{desc}</button>
      </li>
    );
  });

  let status;
  if (winner) {
    status = "Winner: " + winner;
  } else {
    status = "Next player: " + (this.state.xIsNext ? "X" : "O");
  }

  return (
    <div className="game">
      <div className="game-board">
        <Board
          squares={current.squares}
          onClick={i => this.handleClick(i)}
        />
      </div>
      <div className="game-info">
        <div>{status}</div>
        <ol>{moves}</ol>
      </div>
    </div>
  );
}
/// BLAZOR

@{
    var current = this.history[this.stepNumber];
    var winner = CalculateWinner(current.Squares);
    var status = winner != NULL_CHAR
        ? $"Winner: {winner}"
        : $"Next player: {(this.xIsNext ? 'X' : 'O')}";

    Func<object> moves = () =>
    {
        for (int i = 0; i < this.history.Count; i++)
        {
            // Due to a Razor loop quirk, the counter needs to be "captured"
            // https://github.com/dotnet/aspnetcore/issues/16809#issuecomment-549484265
            var move = i;

            var desc = move == 0
                ? "Go to game start"
                : $"Go to move #{move}";

            Action jump = () => this.JumpTo(move);

            <li>
                <button @onclick="jump">@desc</button>
            </li>;
        }

        return null;
    };
}

<div class="game">
    <div class="game-board">
        <Board Squares="current.Squares" OnClick="HandleClick" />
    </div>
    <div class="game-info">
        <div>@status</div>
        <ol>@moves()</ol>
    </div>
</div>

Again, the difference between the 2 rendering approaches are in full display (templating vs. language extension).

Just as the render() method in React creates local variables to prepare data prior to rendering, I used a Razor block (@{}) to declare and initialize local data for the markup. Certainly I could have used a Razor loop (@for) within the markup, but I really wanted to make the markup as readable as possible.

Conclusion

Below are my key observations and opinions about Blazor.

Blazor is a way for .NET devs to catch up to modern web frameworks

If you are a .NET developer looking to create rich web experiences but has been playing catch-up to how rapidly the JavaScript ecosystem has been evolving, Blazor is certainly a great platform. However, be aware that majority of the libraries that facilitate rich web experiences were written, and continue to be written, in JS. Surely there is a way to interop JS and C# in Blazor, but that means waiting for these interop libraries to be written or writing them on your own.

ASP.NET devs need to learn to write declarative view code by default

The most likely adopters of Blazor would be ASP.NET devs experienced in writing Razor code. Even though Razor is a templating language, I see a lot of developers writing imperative logic interspersed with markup. I’m not saying that React developers don’t suffer the same problems, but recommendations and linters have been established to herd devs into writing declarative view code. I feel that the .NET community needs to improve in this front to inject new life into Razor users within Blazor.

Blazor is not a panacea to get out of writing good HTML and CSS

I see a lot of .NET developers who don’t take the time to learn proper CSS, always making the excuse of them not having an eye for design. This mentality must change, and Blazor, nor any new .NET technology will solve that. Only hard work and patience can fix that.

Blazor’s use case is too narrow, that it misses the bigger picture

Blazor (especially Blazor WebAssembly) is an interesting piece of technology. It has a WebAssembly version of the .NET runtime in order to run C# code on the browser. What I can’t understand is why not just stop at that? Why pigeonhole the success of creating a WASM compiled .NET runtime into a web application framework?

WebAssembly’s charm is in turning part of your web application into high performing logic. I do see that compiling the majority of the .NET runtime into a webassembly is not exactly the intended use case but it is such a breakthrough that it would be nice to use C# instead of Rust or C to write performant code on the web.

I just feel that they missed the boat or they are deliberately doing this to cater solely to the .NET community, just like the Microsoft of old.


Written by Anton Mata who lives and works in Manila building useful things. You should follow him on Twitter and GitHub