This article uses a bricks game written (almost) entirely in C#, and with the help of Blazor will explore WebAssembly, discussing what it does and what it does not, how it interacts with JavaScript and how it is generated from the good old C# language. The article covers basics like, what Blazor and Razor are, gives a Blazor demo, and explains how the brick game implements Blazor concepts, one piece at a time (Layout, Lifecycle, Data Binding, Components, and Event Binding).
Click the image to run test the game online!
Introduction
Have you ever wanted to develop and run code for the client side, with programming languages other than JavaScript?
This article uses a bricks game written (almost) entirely in C#, and with the help of Blazor project, we will explore this brave new world of the technology called WebAssembly, discussing what it does and what it does not, how it interacts with JavaScript and how it is generated from the good old C# language.
Background
In recent years, C# language gained new territories due to the release of some interesting technologies and tools, to name a few:
- Xamarin, a tool aimed at cross-platform development for Windows, Android and iOS
- .NET Core, a cross-platform framework running on Windows, Linux or Mac
- ASP.NET Core, a cross-platform web development framework
- Visual Studio Code, a lightweight development environment for working with many languages, C# included
All these tools and frameworks ended up expanding the interest for the C# language.
And now (April 2018), Microsoft is sponsoring an experiment with a new technology that will push further away the limits for C# language applications: the Blazor project.
Many years ago, I developed a simple Tetris-like game engine in C#, which I used in some projects and articles here in Code Project.
When I first heard of the Blazor project, some months ago, I was excited to see how it worked. It was said that Blazor was a Single Page Application (SPA) framework, and the use of WebAssembly produced programs 30 times faster than JavaScript on the browser.
Most of the examples using Blazor I've seen so far include some simple pages, buttons and forms. So I decided to investigate whether it would work with my old Bricks game.
WebAssembly as Client Side Code
If you already wrote client-side code for the web, you know that you have a vast quantity of frameworks and libraries, such as jQuery, ReactJS, Angular, Vue.js and many others. But you always end up writing JavaScript code, which is a very flexible language, although it has its own problems. You can write TypeScript code if you want to bypass some of the issues of JavaScript language, but in the end, it all becomes JavaScript code.
Bringing C# to client-side development to be compiled as WebAssembly
has clear advantages:
- C# is a powerful, feature-rich and robust language, with a huge developer community.
- Developers can reuse existing C# code in the client. Not only their own code, but code from others. (In fact, one of the motives for me to create this project in Blazor and writing this article is the fact that I was successful in reusing existing code.)
- ASP.NET Core is a powerful development framework for the web, and since it uses C# code in the server-side, it would make a lot of sense if C# was used also in the client-side, because this would mean we have a common stack to develop.
What is Blazor?
Blazor is an experimental project created by Steve Sanderson from Microsoft as a Single Page Application (SPA) framework, intended to compile C# code into WebAssembly
.
WebAssembly
is a W3C specification for a binary format running on web browsers, and it is supported and implemented by all popular browsers. And since this specification was published, people have been busy creating compilers from many languages into WebAssembly binary files.
It is said that WebAssembly
runs up to 30 times faster than JavaScript code, due to its near-native performance. But the concept behind Blazor is not to replace JavaScript entirely, but to complement it instead.
We must keep in mind that some JavaScript capabilities cannot be performed by Blazor. As an example, Blazor cannot access directly HTML DOM elements. Instead, Blazor was created as a component-centered framework. Components here are more conceptual than HTML elements that they will generate. So it has to create and manipulate its own components, which in turn produce HTML fragments/elements.
Blazor was inspired in client-side SPA (Single Page Application) frameworks, such as Angular, React.JS and Vue.js, which also share the concept of components.
A project compiled with Blazor runs on the browser over the WebAssembly implementation of the .NET Framework called Mono, which in turn runs on WebAssembly. Some people wonder why the Blazor team chose Mono project instead of .NET Core, and the answer is that, unlike Mono, .NET Core does not contains presentation logic needed to work with different devices and user interfaces.
Razor
Razor was born as an engine that runs on the server and combines C# and HTML templates to generate dynamically the final HTML code that is deployed to the browser.
Blazor, on the other hand, uses Razor during compilation time as a mechanism that combines C# and HTML templates, and as a result generates C# code.
You can see in the picture below how our Index.cshtml Blazor template file was compiled into the Index.g.cs file.
We Have C# Code Generated From HTML. What Now?
But how is C# code recognized by the browser? In fact, it is not, because the browser only knows how to run WebAssembly (.wasm files), which is a specification published by the W3C (World Wide Web Consortium).
So, how does the browser runs our C# code? First, our code is compiled into a .dll, which is a managed code. Blazor will not only deploy your application's .dlls to the browser, but also Mono must be downloaded by the browser.
Mono is an open source of the .NET Framework, used in cross-platform development tools such as Xamarin. The Mono team managed to port Mono to WebAssembly
in the form of a Mono.wasm file, which is deployed to the browser. Then Mono IL (Intermediate Language) is used to run our application's managed code.
You can see from the image below how all these packages are loaded by the web browser booting process:
- First, blazor.js is loaded
- Blazor.js uses Mono's JavaScript library (mono.js)
- Mono.js loads Mono WebAssembly runtime (mono.wasm)
- Mono.wsm, in turn, loads our application DLLs (
BlazorBricks
and BlazorBricks.Core
) and the .NET Framework DLLs.
HTML Generation
Years ago, before HTML5 and CSS3, web applications were much more limited, so people used browser plugins like Flash and Silverlight to provide a more interactive user experience. Flash used a language called ActionScript and Silverlight used C#/VB.NET in the client side. Thanks to HTML5, CSS3 and ES6, both technologies are pretty much dead today.
Because Blazor also uses C# code for the client side, it may seem that Blazor is some sort of Silverlight revival. But fortunately, this is not the case. Silverlight had its own internal render mechanism that used the browser window as a canvas to draw its own interface components.
On the other hand, Blazor relies on browser's Document Object Model (DOM) to present the web pages. But C# alone can't access DOM directly. Instead, it must rely on JavaScript code to manipulate divs, inputs, spans and other DOM elements. You can see how this works by looking at the diagram below:
- A hierarchical structure containing UI components to be displayed (that is, a render tree) is created by the Blazor's C# code, and then the tree is passed to the JavaScript code part of Blazor.
- The Blazor's JavaScript code performs the changes in the DOM according to the structure and contents of the render tree.
- The Blazor JavaScript code listens to all user events, such as mouse click, key pressed, etc. and responds by invoking events implemented by C# code within the application.
- The C# in turn can respond by modifying some parts of the Model (or ViewModel).
- These changes in the model must be reflected in the view, so the JavaScript part of Blazor then analyses the render tree once again and proceeds to apply only the detected differential changes to the DOM.
The Blazor Demo
You can create the standard Blazor Demo from the default Blazor project template.
For now, if you want to develop Blazor project, you are required to:
- Download and install Visual Studio (minimum version: 15.7)
- Search and Install Blazor component Visual Studio extension: ASP.NET Core Blazor Language Services
In order to create a new Blazor project, first choose File > New Project and then select ASP.NET Core Web App:
Next, you will be presented with various possible project types. Select Blazor:
Notice that the above image features also a Blazor - ASP.NET Core hosted type, but that kind of project is quite complex to our needs. So, let's create a new Blazor project instead.
Once I got the brand new Blazor project created, I discovered that it does not allow debugging.... at least for now. Debugging capabilities will show up later, according to the Blazor's team roadmap. So, you should press CTRL+F5 ("Start without debugging") and wait for the server to be launched and the Blazor application to run.
Then the Next step was to create a new class library project to work with Blazor. I just created a new .NET Standard project, which I called "BlazorBricks.Core
", and added a reference to it in the first project.
This was interesting. As you can see in the image below, when you compile the projects, you see how Blazor includes the new .NET Standard .dll as part of the Blazor project /bin folder.
The Blazor Bricks Game
Now let's explain how the game implements Blazor concepts, one piece at a time.
Layout
If you use multiple pages, you should maintain your site layout consistent as the user navigates through pages. The layout in a Blazor project is defined by the component that implements ILayoutComponent
. By default, this role is played by the MainLayout.cshtml file.
@implements ILayoutComponent
@Body
@functions {
public RenderFragment Body { get; set; }
}
Notice that the Body
property is passed as a fragment to the MainLayout
, which in turn will render the HTML body in the appropriate place. Here, I have stripped the MainLayout.cshtml file of any HTML code, but the default and original MainLayout file code contains HTML that is shared across multiple pages.
Lifecycle
When you create and compile a Blazor page (in a .cshtml file), it generates a C# class representation of that page (inside the ./obj/Debug/netstandard2.0/Pages folder). For example, our game implements the Index.cshtml file, which is converted into a class in the Index.g.cs file. The class inherits from Microsoft.AspNetCore.Blazor.Components.BlazorComponent
.
It should be noted that only the binary representation of the page is deployed to the browser. The .cshtml file is not included in the deployment.
Each Blazor page is a BlazorComponent
, and as such it follows the component lifecycle: first, it receives parameters from its parent in the render tree. Then, the OnInitAsync
method of the Index.cshtml page is invoked. But how do we implement this method in C# inside the Blazor .cshtml file? We must include our C# code for that page in a @functions
directive:
@functions {
protected override async Task OnInitAsync()
{
}
}
Now we can implement the C# code for our game. The first step is to create a field that will provide the model for the page. Let's call it boardViewModel
.
@functions {
BlazorBricks.Core.BoardViewModel boardViewModel;
protected override async Task OnInitAsync()
{
boardViewModel = BlazorBricks.Core.GameManager.Instance.CurrentBoard;
}
}
Notice that we assigned to the field an instance of BlazorBricks.Core.BoardViewModel
, that instance is provided by the singleton property BlazorBricks.Core.GameManager.Instance
that comes from the BlazorBricks.Core
project.
Data Binding
Now that you have the data source, it's time to bind it to the view.
For those who already know Razor, this will be quite straightforward, because Razor in Blazor is much like it is in ASP.NET. For instance, let's say you want to render the game score value from the boardViewModel
inside HTML code. That value is provided by the Score
property of the boardViewModel
object. You can simply put the C# expression @boardViewModel.Score
expression in the place where it should be rendered:
<div class="statsLine">
<div>SCORE</div>
<div>@boardViewModel.Score</div>
<hr />
</div>
Now you can follow the same logic to display the rest of the game stats: Hi Score, Lines and Level:
<div class="statsLine">
<div>SCORE</div>
<div>@boardViewModel.HiScore</div>
<hr />
</div>
<div class="statsLine">
<div>SCORE</div>
<div>@boardViewModel.Lines</div>
<hr />
</div>
<div class="statsLine">
<div>SCORE</div>
<div>@boardViewModel.Level</div>
<hr />
</div>
But wait... notice how the HTML code above repeats itself. You may be wondering if there is something we can do about it. Fortunately, Blazor provides the concept of components.
Components
Components allows us to encapsulate HTML fragments - like the ones above - and parameterize them. Blazor components are quite easy to implement. First, create a new .cshtml with the component name you want in the /Shared folder. In our case, the component will be called StatsInfo
. Then copy and paste the HTML fragment you want to be rendered by the component.
<div class="statsLine">
<div>SCORE</div>
<div>@boardViewModel.Score</div>
<hr />
</div>
Then we must parameterize the component. Notice we have two variable data here; the status label and its value. Let's replace the sections with these parameters:
<div class="statsLine">
<div>@Label</div>
<div>@Value</div>
<hr />
</div>
Now those parameters must come from somewhere. Again, we create a @functions
section at the bottom of the document, like we did in the Index
page, and then we provide two properties for the label
and value
parameters:
<div class="statsLine">
<div>@Label</div>
<div>@Value</div>
<hr />
</div>
@functions
{
public string Label { get; set; }
public int Value { get; set; }
}
And then, we have a new component in the /Shared/StatsInfo.cshtml file. Let's use it in the Index.cshtml file as if it was a regular HTML element. Notice how Blazor allows us to pass the parameters in the same way as we assign values to attributes in HTML elements:
<StatsInfo Label="SCORE"
Value="@boardViewModel.Score" />
<StatsInfo Label="HI SCORE"
Value="@boardViewModel.HiScore" />
<StatsInfo Label="LINES"
Value="@boardViewModel.Lines" />
<StatsInfo Label="LEVEL"
Value="@boardViewModel.Level" />
Fortunately, we can also create hierarchies where different types of components can be nested. Let's see how the main bricks board is displayed with the help component nesting:
<div class="board">
<BricksBoard Bricks="@boardViewModel.Bricks" />
</div>
In the above code, @boardViewModel.Bricks
provides the bricks array to be displayed as the game bricks board. But what do we find in BricksBoard.cshtml file?
@foreach (var brick in Bricks)
{
<Brick Color="@brick.Color" />
}
@functions
{
public BlazorBricks.Core.BrickViewModel[] Bricks { get; set; }
}
The foreach
loop in turn displays one Brick
component for each instance in the array.
<span class="colorChip shapecolor-@(Color)"></span>
@functions
{
public string Color { get; set; }
}
Event Binding
At the time I'm writing this article, Blazor supports only two events: onclick
and onchange
. The onclick
event is used in the game to trigger the StartTickLoop
method in the C# functions of the page when the player presses the "Start New Game" button.
<button @onclick(StartTickLoop)>START NEW GAME</button>
The StartTickLoop
, in turn, will call the method with the same name in the BricksPresenter
in BlazorBricks.Core
project.
public void StartTickLoop()
{
BlazorBricks.Core.GameManager.Instance.Presenter.StartTickLoop();
}
The StartTickLoop
method is called to start the game and the main game loop to move down the piece and wait for the player's movements.
Back to events: remember when I said that only onclick
and onchange
events are supported? When I was developing the game, I desperately needed to deal with arrow keys to control the falling piece in the game. But while the Blazor team still hasn't released a complete set of events, there is a workaround to handle key events with Blazor: we can interop with JavaScript in order to fill that gap.
Interop: Calling a C#/.NET method from JavaScript
The JavaScript code below shows how to bind the onkeyup
document in JavaScript, so that we can call a C# method that was compiled by Blazor into WebAssembly
:
<script>
const assemblyName = 'BlazorBricks';
const namespace = 'BlazorBricks';
const typeName = 'OnKeyUp';
const methodName = 'Handler';
const onkeyupMethod = Blazor.platform.findMethod(
assemblyName,
namespace,
typeName,
methodName
);
document.onkeyup = function (evt) {
evt = evt || window.event;
const keyCode = Blazor.platform.toDotNetString(evt.keyCode.toString());
Blazor.platform.callMethod(onkeyupMethod, null, [keyCode]);
};
function onKeyUp(element, evt) {
const char = Blazor.platform.toDotNetString(evt.key)
Blazor.platform.callMethod(onkeyupMethod, null, [char]);
}
</script>
Notice that the code above defines the method Handler
to be called on the OnKeyUp
class. This C# class is quite simple:
public static class OnKeyUp
{
public static Action<string> Action { get; set; }
public static void Handler(string value)
{
Action?.Invoke(value);
}
}
And we define the Action
code inside the OnInitAsync
override method of the page's C# code:
OnKeyUp.Action = async value =>
{
ConsoleKey consoleKey = (ConsoleKey)Enum.Parse(typeof(ConsoleKey), value);
var presenter = BlazorBricks.Core.GameManager.Instance.Presenter;
switch (consoleKey)
{
case ConsoleKey.LeftArrow:
presenter.MoveLeft();
break;
case ConsoleKey.RightArrow:
presenter.MoveRight();
break;
case ConsoleKey.UpArrow:
presenter.Rotate90();
break;
case ConsoleKey.DownArrow:
presenter.MoveDown();
break;
default:
break;
}
this.StateHasChanged();
};
Porting the Old MVC Brics Game
Despite the fact that I was able to reuse my old game code, obviously it wasn't so simple to integrate the whole Bricks engine with the Blazor view. First of all, the original MVC Bricks game contained some AJAX code written in JavaScript to request server side board state with a snapshot of the bricks every fraction of a second. This was a crazy idea - I know - but it worked.
At that time, the client side JavaScript did all the work to request the board state every 1/4 of a second, thus keeping the pace of the game. But for the Blazor project, I modified the Bricks engine code to make the Razor view as passive as possible, by keeping the render loop on the Bricks engine side. Now, the view side of the application only needs to start and stop the Bricks GameManager
, and by doing so, it changes the internal state of the game.
The Game Rules
You must be very familiar with this kind of game, but I have to explain the game rules anyway.
Here, we have an empty 10 x 20 board, that is, containing 200 empty positions. As soon as the game starts, the game engine will randomly generate one new piece at a time, which will fall from the top of the board, falling at the speed of 1 square per second. When the falling piece finds an obstacle (that is, part of another piece that is fixed at the bottom of the board) then it can't fall anymore, so that falling piece gets stuck. Then the game engine will produce new random pieces, and they are piled up until the pile reaches the top of the board, and at this moment, the game ends. The user will have to control each falling piece, by moving it to the left, to the right, or rotating it, placing the new piece in the lowest possible empty place in the board where the new piece fits in, in a way to avoid the piled pieces to reach the top of the board. Also, when the user fills any the board rows, these rows are cleared, thus giving some extra space and prolonging the game.
| | | |
The "I" shape | The "L" shape | The "J" shape | The "O" shape |
| | | |
The "T" shape | The "S" shape | The "Z" shape | |
The game engine can randomly generate any of the above shapes. As we can see, each shape is associated with a letter which resembles it.
For each cleared line, the user scores a total of 10 multiplied by the game level. That is, each row cleared in the first level will give 10 points. The second level will give 20 points per cleared row, and so on.
Each level is completed when the user has cleared 10 rows. That is, to reach the 5th level, the user must have cleared 40 rows.
When the game is over, the game score is compared with the previous high score, and replaces it if there is a new record.
The Next piece gives the user the opportunity to place the current piece in a way that makes it easier to accommodate the next falling piece.
The Model
The model is defined by the BoardViewModel
and BrickViewModel
classes, and contains all information needed by the View to render the game board, the score board and to know whether the game is over or not. As we can see below, most of the properties of BoardViewModel
class are native types, except for the Bricks
and Next
properties, which are 2-dimensional arrays of the BrickViewModel
and hold the data for the bricks and empty spaces that forms the current snapshot of the game board and the bricks corresponding to the Next
piece that will fall from the top of the game board.
The low-level BrickViewModel
class has information about each individual brick: row, column and the color name. These values will be used by the View to find the corresponding divs and update their background color accordingly.
public class BrickViewModel
{
public int Row { get; set; }
public int Col { get; set; }
public string Color { get; set; }
}
public class BoardViewModel
{
public BoardViewModel()
{
IsGameOver = false;
}
public BrickViewModel[] Bricks { get; set; }
public int Score { get; set; }
public int HiScore { get; set; }
public int Lines { get; set; }
public int Level { get; set; }
public BrickViewModel[] Next { get; set; }
public bool IsGameOver { get; set; }
}
}
The Game Manager
The GameManager
is a class that holds all methods needed by the BricksController
so that the BricksView
requests can be communicated to the game engine, and the responses can be sent back to the BricksView
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MVCBricks.Core
{
public class GameManager : MVCBricks.Core.IView
{
private static GameManager instance = null;
private static BricksPresenter presenter = null;
private static BoardViewModel currentBoard = null;
private GameManager()
{
currentBoard = new BoardViewModel();
currentBoard.Bricks = new BrickViewModel[] { };
presenter = new BricksPresenter(this);
presenter.InitializeBoard();
presenter.Tick();
}
When the DisplayScore
is called, all the scoreboard data are gathered so that they can be consumed by the view through a single call from the controller.
public void DisplayScore(int score, int hiScore, int lines,
int level, MVCBricks.Core.Shapes.IShape next)
{
currentBoard.Score = score;
currentBoard.HiScore = hiScore;
currentBoard.Lines = lines;
currentBoard.Level = level;
currentBoard.Next = GetBricksArray(next.ShapeArray.GetUpperBound(1) + 1,
next.ShapeArray.GetUpperBound(0) + 1, next.ShapeArray);
}
The GetBricksArray
method converts both the game board bricks array and the next shape array into a system of colors which the view can understand.
private BrickViewModel[] GetBricksArray(int rowCount, int colCount, IBrick[,] array)
{
var bricksList = new List<BrickViewModel>();
for (var row = 0; row < rowCount; row++)
{
for (var col = 0; col < colCount; col++)
{
var b = array[col, row];
if (b != null)
{
bricksList.Add(new BrickViewModel()
{
Row = row,
Col = col,
Color = b.Color.ToString().Replace("Color [", "").Replace("]", "")
});
}
else
{
bricksList.Add(new BrickViewModel()
{
Row = row,
Col = col,
Color = "rgba(0, 0, 0, 1.0)"
});
}
}
}
return bricksList.ToArray();
}
Further Reading
Conclusion
I hope you have enjoyed reading the article, even if just to get a little glimpse about Blazor. Obviously, since at this point in time Blazor is still in pre-alpha stage, it has a lot of issues to solve and improvements to make, but I see a huge potential in Blazor in inviting so many C# developers to WebAssembly
development.
Please download the source code from the link at the top of the article. Do you like it? Are there any suggestions, tips or complaints about it? Please leave a comment in the section below, I’m eager to read your feedback! :-)
History
- 11th May, 2018: Initial version