:::: MENU ::::

Thursday, February 8, 2018

Today the ASP.NET team announced that Blazor has moved into the ASP.NET organization, and we're beginning an experimental phase to see whether we can develop it into a supported shipping product. This is a big step forwards!

What is Blazor? It's a framework for browser-based (client-side) applications written in .NET, running under WebAssembly. It gives you all the benefits of a rich, modern single-page application (SPA) platform while letting you use .NET end-to-end, including sharing code across server and client. The announcement post covers more about the intended use cases, timescales, and so on.

In this blog post, I want to provide some deeper technical details for those interested in how it actually works.

Running .NET in the browser

The first step to building a .NET-based SPA framework is to have a way of running .NET code inside web browsers. We can at last do this based on open standards, supporting any web browser (without any plugins), thanks to WebAssembly.

WebAssembly is now supported by all mainstream browsers, including on mobile devices. It's a compact bytecode format optimised for minimum download sizes and maximum execution speed. Despite what a lot of developers first assume, it does not introduce new security concerns, because it isn't regular assembly code (e.g., x86/x64 or similar) - it's a new bytecode format that can only do what JavaScript can do.

So how does that let us run .NET? Well, the Mono team is adding support for WebAssembly. In case you missed the news, Mono has been part of Microsoft since 2016. Mono is the official .NET runtime for client platforms (e.g., native mobile apps and games). WebAssembly is yet another client platform, so it makes sense that Mono should run on it.

Mono aims to run under WebAssembly in two modes: interpreted and AOT.

Interpreted mode

In interpreted mode, the Mono runtime itself is compiled to WebAssembly, but your .NET assembly files are not. The browser can then load and execute the Mono runtime, which in turn can load and execute standard .NET assemblies (regular .NET .dll files) built by the normal .NET compilation toolchain.

Diagram showing interpreted mode

This is similar to how, for the desktop CLR, the core internals of the CLR are distributed precompiled to native code, which then loads and executes .NET assembly files. One key difference is that the desktop CLR uses just-in-time (JIT) compilation extensively to make execution faster, whereas Mono on WebAssembly is closer to a pure interpretation model.

Ahead-of-time (AOT) compiled mode

In AOT mode, your application's .NET assemblies are transformed to pure WebAssembly binaries at build time. At runtime, there's no interpretation: your code just executes directly as regular WebAssembly code. It's still necessary to load part of the Mono runtime (e.g., parts for low-level .NET services like garbage collection), but not all of it (e.g., parts for parsing .NET assemblies).

Diagram showing AOT mode

This is similar to how the ngen tool has historically allowed AOT compilation of .NET binaries to native machine code, or more recently, CoreRT provides a complete native AOT .NET runtime.

Interpreted versus AOT

Which mode is best? We don't know yet.

What we do know is that interpreted mode provides a much faster development cycle than AOT. When you change your code, you can rebuild it using the normal .NET compiler and have the updated application running in your browser in seconds. An AOT rebuild, on the other hand, might take minutes.

So one obvious thought is that interpreted mode might be for development, and AOT mode might be for production.

But that might not turn out to be the case, because interpreted mode is surprisingly much faster than you'd think, and we hear from Xamarin folks who use .NET for native mobile apps that regular (non-AOT) .NET assemblies are very small and compression-friendly compared with AOT-compiled assemblies. We're keeping our options open until we can measure the differences objectively.

Blazor, a SPA framework

Being able to run .NET in the browser is a good start, but it's not enough. To be a productive app builder, you'll need a coherent set of standard solutions to standard problems such as UI composition/reuse, state management, routing, unit testing, build optimisation, and much more. These should be designed around the strengths of .NET and the C# language, making the most of the existing .NET ecosystem, and packaged with the first-rate tooling support that .NET developers expect.

Blazor provides that. It's inspired by today's top SPA frameworks such as React, Vue, and Angular, plus some Microsoft UI stacks such as Razor Pages. The goal is to provide what web developers have found most successful in a way that fits neatly with .NET.

Components

In all recent SPA frameworks, applications are built out of components. A component usually represents a piece of UI, such as a page, a dialog, a tabset, or a data entry form. Components can be nested, reused, and shared between projects.

In Blazor, a component is a .NET class, which you can either write directly (e.g., as a C# class) or more commonly in the form of a Razor markup page (i.e., a .cshtml file).

Razor, which has been around since 2010, is a syntax for combining markup with C# code. It's designed completely for developer productivity, letting you switch between markup and C# without ceremony, with complete intellisense support throughout. Here's an example of how you could express a simple custom dialog component as a Razor file called MyDialog.cshtml:

<div class="my-styles">    <h2>@Title</h2>    @RenderContent(Body)    <button onclick=@OnOK>OK</button>  </div>    @functions {      public string Title { get; set; }      public Content Body { get; set; }      public Action OnOK { get; set; }  }  

When you want to use this component elsewhere, the tooling knows what you're doing:

Animated GIF of Blazor component tooling

Many design patterns are possible on this simple foundation. This includes patterns you might recognise from other SPA frameworks, such as stateful components, functional stateless components, and higher-order components. You can nest components, generate them procedurally, share them via class libraries, unit test them without needing any browser DOM, and generally have a good life.

Infrastructure

When you create a new project, Blazor will offer the core facilities that most applications need. This includes:

  • Layouts
  • Routing
  • Dependency injection
  • Lazy loading (i.e., loading parts of the app on demand as a user navigates)
  • Unit testing harness

As an important design point, all of these are optional. If you don't use some of them, the implementation is stripped out of your app on publish by the IL linker.

Another important design point is that only a few very low-level pieces are baked into the framework. For example, routing and layouts are not baked in: they are implemented in "user space", i.e., code that an application developer can write without using any internal APIs. So if you don't like our routing or layout systems, you can replace them with your own. Our current layouts prototype is implemented in only about 30 lines of C#, so you could easily understand and replace it if you wanted.

Deployment

Obviously a major part of the target market for Blazor is ASP.NET developers. For those, we'll ship ASP.NET middleware so you can serve a Blazor UI seamlessly, plus get advanced features like server-side prerendering.

Equally important to us are developers who don't yet use .NET for anything. To make Blazor a viable consideration for developers using Node.js, Rails, PHP, or anything else on the server, or even for serverless web apps, we absolutely don't require you to use .NET on the server. A Blazor app, when built, produces a dist/ directory containing nothing but static files. You can host this on GitHub pages, cloud storage services, from Node.js servers, or anything else you like.

Code sharing and netstandard

.NET standard is way of saying what capability level a .NET runtime provides, or what capability level a .NET assembly file requires. If your .NET runtime supports netstandard2.0 and below, and you have an assembly that targets netstandard2.0 or above, then you can use that assembly on that runtime.

Mono on WebAssembly will support netstandard2.0 or some higher version (depending release timeframes). This means you can share your standard .NET class libraries across backend server code and in browser-based apps. For example, you could have a project containing your business's domain model classes and use it both on server and client. It also means you can pull in packages from NuGet.

However, not all .NET APIs make sense in the browser. For example, you can't listen on arbitrary TCP sockets in a browser, so System.Net.Sockets.TcpListener can't do anything useful. Likewise you pretty much certainly shouldn't be using System.Data.SqlClient directly from a browser app. This is OK because (1) browsers do support the APIs that people actually need to build web apps, and (2) because .NET standard has a way of accounting for this. For APIs that don't apply to a given platform, the base class library (BCL) will throw a PlatformNotSupported exception. In the short term there will be cases where this is not ideal, but over time NuGet package authors will make adjustments to allow for different platforms. If .NET wants to expand into the world's most widely deployed app platform then this is an inevitable stepping stone.

JavaScript/TypeScript interop

Even if you're building your browser-based app in C#/F#, you'll still sometimes want to use third-party JavaScript libraries, or directly put in a bit of your own JavaScript/TypeScript to reach newly-emerging browser APIs.

This should be very simple, as (not surprisingly) WebAssembly is designed to interoperate with JavaScript, and we can expose that nicely to .NET code.

To work with third-party JavaScript libraries, we're exploring the option of using TypeScript type definitions to present them to your C# code with full intellisense. This would make the top 1000-or-so JS libraries trivial to consume.

To call other JS libraries or your own custom JS/TS code from .NET, the current approach is to register a named function in a JavaScript/TypeScript file, e.g.:

// This is JavaScript  Blazor.registerFunction('doPrompt', message => {    return prompt(message);  });  

… and then wrap it for calls from .NET:

// This is C#  public static bool DoPrompt(string message)  {      return RegisteredFunction.Invoke<bool>("doPrompt", message);  }  

The registerFunction approach has the benefit of working nicely with JavaScript build tools such as Webpack.

And to save you the trouble of doing this for standard browser APIs, the Mono team is working on a library that exposes standard browser APIs to .NET.

Optimisation

Traditionally, .NET has focused on platforms where application binary size isn't a major concern. It doesn't really matter whether your server-side ASP.NET application is 1MB or 50MB. It's only a moderate concern for native desktop or mobile apps. But for browser apps, payload size is critical.

An argument people have made is that, for .NET on WebAssembly, the download is pretty much a one-time thing. We can use normal browser HTTP caching (or fancy service worker stuff if you like) to ensure a given user only fetches the core runtime once. If it's on a CDN, the user only pays the cost once across all .NET-based web apps.

It's true, but I don't think that's good enough on its own. If it's 20MB, it's still much too big even if the user only fetches it once. This is not meant to feel like a browser plugin, after all - it's a standards-compliant web app. The first-ever load must be fast.

As such we're putting a lot of thought into getting the download size down. Here are three phases of size optimisation we have in mind:

1. Mono runtime stripping

The Mono runtime itself contains a lot of desktop-specific features. We hope that the Blazor packages will contain a trimmed version of Mono that is substantially smaller than the full-fat distribution. In a hand-optimisation experiment, I was able to remove over 70% of the mono .wasm file while keeping a basic app working.

2. Publish-time IL stripping

The .NET IL linker (originally based on the Mono linker) does static analysis to figure out which parts of .NET assemblies can ever get called by your app, then it strips out everything else.

This is equivalent to tree shaking in JavaScript, except the IL linker is much more fine-grained, operating at the level of individual methods. This removes all the system library code you're not using and makes a huge difference in typical cases, often cutting out another 70+% of the remaining app size.

3. Compression

Finally, and most obviously, we expect your web server to support HTTP compression. This typically cuts the remaining payload size by a further 75%.

Overall, a .NET-based browser app is never going to be as tiny as a minimal React app, but the goal is to make it small enough that a typical user on an average connection won't notice or care even on their first ever load, and of course things will be cached fully for subsequent loads.

What's the point of it all?

Whether it pleases you or not, web development is going to change over the next few years. WebAssembly will allow web developers to choose from a much wider range of languages and platforms than ever before. This is a good thing - our world is finally growing up! Developers building server-side software or native apps have always been able to pick languages and programming paradigms that best match their target problem and their team culture and background. Do you like functional programming with Haskell or Lisp for your financial app? Need some low-level C? More of an Apple dev and want to reuse your Swift skills? They're all coming to the web.

Don't be overwhelmed. It doesn't mean you have to know all languages. It just means we all get to be regular software developers. Your existing expertise with browser programming is still relevant and valuable, but you'll have more ways of expressing your ideas and more connections with other software communities.

So our initiative here is to put .NET up near the front, rather than trailing years behind.

Current status

Feeling keen to give this a go? Whoa there - we're still very early in this project. There isn't yet a download, and most of the above is still a work in progress. Most of you should just relax and wait for the first pre-alpha bits to appear in another month or so.

Remember, Blazor so far is an experiment of the ASP.NET team. We're taking some months to figure out whether we can make a supported, shipping product of this. As yet it's not committed, so don't base your business plans around it!

If you're super keen, please have a look the repo, try building it, run the tests, and talk to us. Maybe even find a // TODO comment and submit a PR, or tell us about your great feature ideas.

More