<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Steve Sanderson's Blog</title>
    <description>Steve Sanderson's Blog</description>
    <link></link>
    <atom:link href="https://blog.stevensanderson.com/feed/" rel="self" type="application/rss+xml" />
    
      <item>
        <title>Using gRPC-Web with Blazor WebAssembly</title>
        <description>&lt;p&gt;&lt;em&gt;If you already know all about gRPC and gRPC-Web, you can skip ahead to &lt;a href=&quot;#adding-grpc-services-to-a-blazor-webassembly-application&quot;&gt;adding gRPC services to a Blazor WebAssembly application&lt;/a&gt;. And if you just want some sample Blazor WebAssembly + gRPC-Web apps, see &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorGrpcSamples&quot;&gt;this repo&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-status-quo&quot;&gt;The status quo&lt;/h2&gt;

&lt;p&gt;In Blazor WebAssembly, like in all other browser-based single-page app (SPA) technologies, by far the most common way to exchange data and trigger server-side operations is &lt;em&gt;JSON-over-HTTP&lt;/em&gt;. It’s simple: the client makes an HTTP request to some pre-agreed URL, with a pre-agreed HTTP method, sending JSON data in a pre-agreed shape, and then the server performs an operation and replies with a pre-agreed HTTP-status code and JSON data in a pre-agreed shape.&lt;/p&gt;

&lt;p&gt;This generally works well, and people who do it can often lead fulfilling lives&lt;sup id=&quot;fnref:lives&quot;&gt;&lt;a href=&quot;#fn:lives&quot; class=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. However, two weaknesses stand out:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;JSON is a &lt;em&gt;very&lt;/em&gt; verbose data format. It’s not optimised for bandwidth.&lt;/li&gt;
  &lt;li&gt;There’s no mechanism to guarantee that all these pre-agreed details of URLs, HTTP methods, status codes (etc.) are actually consistent between server and client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;whats-grpc&quot;&gt;What’s gRPC?&lt;/h2&gt;

&lt;p&gt;gRPC is a Remote Procedure Call (RPC) mechanism originally developed by Google. For the purposes of a SPA, you can think of it as an alternative to JSON-over-HTTP. It directly fixes the two weaknesses listed above:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It’s optimised for minimal network traffic, sending efficient binary-serialised messages&lt;/li&gt;
  &lt;li&gt;You can guarantee at compile time that the server and client agree about what endpoints exist, and what shape of data will be sent and received, without having to specify any URLs/status codes/etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So how does this work? To author a gRPC service, you write a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.proto&lt;/code&gt; file which is a language-independent description of a set of RPC services and their data shapes. From this, you can code-generate strongly-typed server and client classes in whatever language you wish, so that conformity with your protocol is guaranteed at compile time. Then at runtime, gRPC deals with the (de)serialization of your data and sends/receives messages in an efficient format (protobuf by default).&lt;/p&gt;

&lt;p&gt;Another great benefit is that it’s &lt;em&gt;not&lt;/em&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Representational_state_transfer&quot;&gt;REST&lt;/a&gt;, so you don’t have to have constant arguments with coworkers about which HTTP methods and status codes are the most blessed and auspicious ones for your scenario. It’s just simple RPC, which is all we’ve ever really wanted since we were little kids.&lt;/p&gt;

&lt;h2 id=&quot;why-isnt-everyone-already-using-grpc-in-their-spas&quot;&gt;Why isn’t everyone already using gRPC in their SPAs?&lt;/h2&gt;

&lt;p&gt;Traditionally it’s not been possible to use gRPC from browser-based applications, because gRPC requires HTTP/2, and browsers don’t expose any APIs that let JS/WASM code control HTTP/2 requests directly.&lt;/p&gt;

&lt;p&gt;But there is a solution! gRPC-Web is an extension to gRPC which makes it compatible with browser-based code (technically, it’s a way of doing gRPC over HTTP/1.1 requests). gRPC-Web hasn’t become prevalent yet because not many server or client frameworks have offered support for it… &lt;em&gt;until now&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;ASP.NET Core has offered great gRPC support since the 3.0 release. &lt;strong&gt;And now, building on this, we’re about to ship preview support for gRPC-Web on both server and client.&lt;/strong&gt; If you want to dig into the details, &lt;a href=&quot;https://github.com/grpc/grpc-dotnet/pull/695&quot;&gt;here’s the excellent pull request from James Newton-King where it’s all implemented&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;adding-grpc-services-to-a-blazor-webassembly-application&quot;&gt;Adding gRPC services to a Blazor WebAssembly application&lt;/h1&gt;

&lt;p&gt;There’s no project template for this yet, so &lt;strong&gt;adding gRPC support to a Blazor WebAssembly app involves quite a lot of steps, detailed here&lt;/strong&gt;. But the good news is that you only have to do this setup once. When you’re up and running, adding further gRPC endpoints and calling them is absolutely trivial.&lt;/p&gt;

&lt;p&gt;First, since the gRPC-Web packages haven’t been published to NuGet.org yet, for now you’ll need to add a couple of temporary package feeds to get the nightly previews.&lt;/p&gt;

&lt;p&gt;You can add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NuGet.config&lt;/code&gt; file at the root of your solution &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorGrpcSamples/blob/master/nuget.config&quot;&gt;like this one&lt;/a&gt;. Hopefully a month or two from now this won’t be needed.&lt;/p&gt;

&lt;h1 id=&quot;adding-grpc-services-to-a-blazor-webassembly-hosted-application&quot;&gt;Adding gRPC services to a Blazor WebAssembly “hosted” application&lt;/h1&gt;

&lt;p&gt;If you’re hosting a Blazor WebAssembly application on ASP.NET Core server, then by default you have three projects: &lt;em&gt;client&lt;/em&gt;, &lt;em&gt;server&lt;/em&gt;, and &lt;em&gt;shared&lt;/em&gt;. I’ve found that the most convenient place to define a gRPC service is in the &lt;em&gt;shared&lt;/em&gt; project, because then its generated classes are available to both server and client.&lt;/p&gt;

&lt;p&gt;To get started, edit your &lt;em&gt;shared&lt;/em&gt; project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt; to add references to the necessary gRPC packages:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Google.Protobuf&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;3.11.2&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Grpc.Net.Client&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2.27.0-dev202001100801&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Grpc.Tools&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2.27.0-dev202001081219&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;PrivateAssets&amp;gt;&lt;/span&gt;all&lt;span class=&quot;nt&quot;&gt;&amp;lt;/PrivateAssets&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span class=&quot;nt&quot;&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If these packages won’t restore correctly, be sure you &lt;a href=&quot;#adding-grpc-services-to-a-blazor-webassembly-application&quot;&gt;added the nightly feeds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now you can create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.proto&lt;/code&gt; file to define your services. For example, inside your &lt;em&gt;shared&lt;/em&gt; project add a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;greet.proto&lt;/code&gt;, containing this:&lt;/p&gt;

&lt;div class=&quot;language-proto highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;syntax&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;proto3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;option&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;csharp_namespace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;GrpcGreeter&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;greet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Greeter&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;rpc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SayHello&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HelloRequest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;returns&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HelloReply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HelloRequest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HelloReply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To make the gRPC tooling generate server and client classes from this, go into your &lt;em&gt;shared&lt;/em&gt; project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt; and add this:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;nt&quot;&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;Protobuf&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;greet.proto&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The solution should build without errors at this point.&lt;/p&gt;

&lt;h3 id=&quot;exposing-a-grpc-service-from-the-server&quot;&gt;Exposing a gRPC service from the server&lt;/h3&gt;

&lt;p&gt;In your &lt;em&gt;server&lt;/em&gt; project, create a new class called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GreeterService&lt;/code&gt;, containing this:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Grpc.Core&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;GrpcGreeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GreeterService&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GreeterBase&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HelloReply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SayHello&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HelloRequest&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ServerCallContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HelloReply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&quot;Hello from gRPC, &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;!&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FromResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This class inherits from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Greeter.GreeterBase&lt;/code&gt;, which is generated for you automatically from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.proto&lt;/code&gt; file. So when you add new endpoints to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.proto&lt;/code&gt;, there will be new methods for you to override here to provide an implementation.&lt;/p&gt;

&lt;p&gt;The last bit on the server is to expose this as a gRPC-Web service using the new APIs. You’ll need to reference a package for this, so in your &lt;em&gt;server&lt;/em&gt; project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt;, add the following package references:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Grpc.AspNetCore&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2.27.0-dev202001100801&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Grpc.AspNetCore.Web&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2.27.0-dev202001100801&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now in your server’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt; file, modify &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConfigureServices&lt;/code&gt; to add the following line:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddGrpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Note: If you’re &lt;em&gt;only&lt;/em&gt; going to expose gRPC services, you might not need MVC controllers any more, in which case you could remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;services.AddMvc()&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;endpoints.MapDefaultControllerRoute()&lt;/code&gt; from below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just underneath &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.AddRouting();&lt;/code&gt; add the following, which deals with mapping incoming gRPC-Web requests to look like gRPC ones to the server:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseGrpcWeb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, register your gRPC-Web service class inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.UseEndpoints&lt;/code&gt; block with the following line at the top of the block:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;endpoints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MapGrpcService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GreeterService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;EnableGrpcWeb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it, your gRPC-Web server is ready!&lt;/p&gt;

&lt;h3 id=&quot;consuming-a-grpc-service-from-the-client&quot;&gt;Consuming a gRPC service from the client&lt;/h3&gt;

&lt;p&gt;In your &lt;em&gt;client&lt;/em&gt; project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt;, you’ll want to add references to the following two nightly packages:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Grpc.Net.Client.Web&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2.27.0-dev202001100801&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Microsoft.AspNetCore.Blazor.Mono&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;3.2.0-preview1.20052.1&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The latter one is to work around an issue in Blazor WebAssembly that will be fixed in the next preview in a couple of weeks. If these packages won’t restore correctly, be sure you &lt;a href=&quot;#adding-grpc-services-to-a-blazor-webassembly-application&quot;&gt;added the nightly feeds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now set up your client app’s &lt;em&gt;dependency injection&lt;/em&gt; system to be able to provide instances of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GreeterClient&lt;/code&gt;. This will let you invoke the gRPC service from anywhere in your client app. In the &lt;em&gt;client&lt;/em&gt; project’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt;, add the following inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConfigureServices&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSingleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Create a gRPC-Web channel pointing to the backend server&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GrpcWebHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GrpcWebMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GrpcWeb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HttpClientHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baseUri&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NavigationManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BaseUri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GrpcChannel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ForAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;baseUri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GrpcChannelOptions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Now we can instantiate gRPC clients for this channel&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GreeterClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Greeter.GreeterClient&lt;/code&gt; is code-generated for you from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.proto&lt;/code&gt; file. You don’t have to implement it manually! But you will also need to add the following &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;using&lt;/code&gt; statements to make the above code compile:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;GrpcGreeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Grpc.Net.Client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Grpc.Net.Client.Web&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Microsoft.AspNetCore.Components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;System.Net.Http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’re very nearly there now! There’s a working server, and hopefully a working client. We just have to actually call some gRPC services from some UI. For example, in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Index.razor&lt;/code&gt; file, replace the contents with this:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;@page&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;@using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GrpcGreeter&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;@inject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GreeterClient&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GreeterClient&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gRPC&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;@bind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;yourName&quot;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Type your name&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;@onclick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;GetGreeting&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;=&quot;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;btn&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;btn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;primary&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&amp;gt;Call gRPC service&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;@serverResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strong&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;@code&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yourName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Bert&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverResponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetGreeting&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HelloRequest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yourName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reply&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GreeterClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SayHelloAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;serverResponse&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Try it out in a browser now. The UI will look like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2020/01/15/greeter-ui.png&quot; alt=&quot;Greeter UI&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;… and if you check out the requests in the dev tools’ network tab, you’ll see it’s sending and receiving binary protobuf messages:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2020/01/15/greeter-request.png&quot; alt=&quot;Greeter request&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;summary-and-sample&quot;&gt;Summary and sample&lt;/h3&gt;

&lt;p&gt;Now you’ve got the basics in place, you can go much further and use gRPC for all data exchange between server and client, if you wish. The gRPC tooling will code-generate all the data transfer classes for you, make the network traffic more efficient, as well as eliminate HTTP-over-JSON concerns like URLs, HTTP methods, status codes, and serialization.&lt;/p&gt;

&lt;p&gt;For a fractionally more detailed example, &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorGrpcSamples/tree/master/Hosted&quot;&gt;here’s a complete Blazor WebAssembly hosted application that uses gRPC for fetching “weather forecast” data&lt;/a&gt;. If you’re interested in the exact steps needed to upgrade from the default JSON-based solution to a gRPC-Web one, see &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorGrpcSamples/commit/72544c54085a35cd89aae20030d7f91d75317a2f&quot;&gt;this diff that shows exactly what I changed&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;adding-grpc-services-to-a-blazor-webassembly-standalone-application&quot;&gt;Adding gRPC services to a Blazor WebAssembly “standalone” application&lt;/h1&gt;

&lt;p&gt;If you’re building a pure standalone Blazor WebAssembly application - not &lt;em&gt;hosted on ASP.NET Core&lt;/em&gt; - then we can’t make any assumptions about what kind of server you will have. Let’s just say you have some gRPC-Web compatible service endpoint to call. Maybe it’s exposed by an ASP.NET Core server on some other host, or maybe it’s an &lt;a href=&quot;https://blog.envoyproxy.io/envoy-and-grpc-web-a-fresh-new-alternative-to-rest-6504ce7eb880&quot;&gt;Envoy gRPC-Web wrapper&lt;/a&gt; around another gRPC service. The only thing we’re concerned about here is configuring your Blazor WebAssembly application to consume it.&lt;/p&gt;

&lt;p&gt;Most of the steps for setting up your client app are &lt;a href=&quot;#consuming-a-grpc-service-from-the-client&quot;&gt;the same as in the “hosted” case above&lt;/a&gt;. However in some ways it’s a bit trickier because we can’t rely on some of the assumptions we can make in the “hosted” case. Here are the differences:&lt;/p&gt;

&lt;h3 id=&quot;getting-and-using-the-proto-file&quot;&gt;Getting and using the .proto file&lt;/h3&gt;

&lt;p&gt;Presumably your external gRPC service maintainer can supply you with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.proto&lt;/code&gt; file defining that service. It’s up to you to copy that into your client project and add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;Proto&amp;gt;&lt;/code&gt; item referencing it in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt;. For the tooling to work, you’ll also need to &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorGrpcSamples/commit/d6ec609f2b7e6591958d38e4a207c9b4f52f0feb#diff-32726408c1966e2a47b19fa484d2f1b0&quot;&gt;add package references like these&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;configuring-the-client-di-service&quot;&gt;Configuring the client DI service&lt;/h3&gt;

&lt;p&gt;Like in the “hosted” case, you’ll add code to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt; to add a gRPC-Web client service. The main difference this time is that you have to know what base URL is used to reach your external service, since we can no longer assume it’s the same base URL that your client app is being served from. So, your DI service entry may look more like the following:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddSingleton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#if DEBUG
&lt;/span&gt;    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backendUrl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://localhost:5001&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Local debug URL&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#else
&lt;/span&gt;    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;backendUrl&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;https://some.external.url:12345&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Production URL&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#endif
&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Now we can instantiate gRPC clients for this channel&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GrpcWebHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GrpcWebMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GrpcWeb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;HttpClientHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GrpcChannel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ForAddress&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;backendUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GrpcChannelOptions&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HttpClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;httpClient&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Greeter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GreeterClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;channel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This varies the URL based on whether you built in debug mode or not.&lt;/p&gt;

&lt;h3 id=&quot;summary-and-sample-1&quot;&gt;Summary and sample&lt;/h3&gt;

&lt;p&gt;That’s it! Now your standalone Blazor WebAssembly application can consume an external gRPC-Web service. For a complete runnable example, &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorGrpcSamples/tree/master/Standalone&quot;&gt;here’s a sample standalone app that invokes a gRPC-Web service on an external URL&lt;/a&gt;. This sample comes with an actual gRPC-Web server for testing too, but you can consider that separate. If you want to see exactly what I changed, &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorGrpcSamples/commit/d6ec609f2b7e6591958d38e4a207c9b4f52f0feb&quot;&gt;here’s the diff versus the default project template output&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;your-feedback-requested&quot;&gt;Your feedback requested&lt;/h1&gt;

&lt;p&gt;If you want to go further with gRPC, see the &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/grpc/?view=aspnetcore-3.1&quot;&gt;ASP.NET Core gRPC docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Please give us feedback about your opinions and experiences with gRPC-Web, as this will help us make a choice about how and whether to make gRPC-Web a standard feature in future versions of ASP.NET Core. You can post feedback either as comments here, or perhaps better still &lt;a href=&quot;https://github.com/dotnet/aspnetcore/issues&quot;&gt;on GitHub as an issue with “feedback” in the title&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:lives&quot;&gt;
      &lt;p&gt;&lt;em&gt;Fulfilling lives not guaranteed&lt;/em&gt; &lt;a href=&quot;#fnref:lives&quot; class=&quot;reversefootnote&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Wed, 15 Jan 2020 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2020/01/15/2020-01-15-grpc-web-in-blazor-webassembly/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2020/01/15/2020-01-15-grpc-web-in-blazor-webassembly/</guid>
      </item>
    
      <item>
        <title>Meet WebWindow, a cross-platform webview library for .NET Core</title>
        <description>&lt;p&gt;My &lt;a href=&quot;/2019/11/01/exploring-lighter-alternatives-to-electron-for-hosting-a-blazor-desktop-app/&quot;&gt;last post&lt;/a&gt; investigated ways to build a .NET Core desktop/console app with a web-rendered UI &lt;em&gt;without&lt;/em&gt; bringing in the full weight of Electron. This seems to have interested a lot of people, so I decided to upgrade it to newer technologies and add cross-platform support.&lt;/p&gt;

&lt;p&gt;The result is a little NuGet package called &lt;a href=&quot;https://www.nuget.org/packages/WebWindow&quot;&gt;WebWindow&lt;/a&gt; that you can add to any .NET Core console app. It can open a native OS window (Windows/Mac/Linux) containing web-based UI, without your app having to bundle either Node or Chromium.&lt;/p&gt;

&lt;p&gt;I’ve also decoupled it from Blazor. You can now host any kind of web UI inside the window. The repo contains a sample that uses Vue.js, and another that uses Blazor.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Caution:&lt;/strong&gt; This library is super-pre-alpha quality. If you’re thinking of building something real with this, see the notes at the end of this post. So far, this is just another prototype.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;hello-world-example&quot;&gt;“Hello World” example&lt;/h1&gt;

&lt;p&gt;Create a new .NET Core 3 C# console application, and then add a reference to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebWindow&lt;/code&gt; NuGet package:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;WebWindow&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0.1.0-20191120.3&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, add code to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Main&lt;/code&gt; method in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program&lt;/code&gt; class.&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;WebWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;My super app&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NavigateToString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt; This window is from a .NET Core app.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;window&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WaitForExit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it! Now depending on which OS you’re running, your app will display a window like one of the following:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/18/hello-windows.png&quot; alt=&quot;WebWindow Hello World example on Windows&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/18/hello-macos.png&quot; alt=&quot;WebWindow Hello World example on macOS&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/18/hello-ubuntu.png&quot; alt=&quot;WebWindow Hello World example on Ubuntu Linux&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The example here uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NavigateToString(html)&lt;/code&gt; to render some HTML from a hardcoded .NET &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;string&lt;/code&gt;. You can also use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NavigateToUrl(url)&lt;/code&gt; to display content from an HTTP server (local or remote)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NavigateToLocalFile(path)&lt;/code&gt; to display an HTML file from the local disk, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;path&lt;/code&gt; is absolute or relative to the current working directory. The HTML file can reference other resources such as images, JS, CSS, etc., relative to its own location on disk. &lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow/blob/a01537a9328b085075866a965191d6323ad2cf7d/samples/HelloWorldApp/Program.cs#L11&quot;&gt;Example here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a slightly more advanced option, you can configure the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WebWindow&lt;/code&gt; to handle a custom scheme such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myapp://&lt;/code&gt; and specify a delegate (callback) that returns arbitrary content for each URL within that scheme. &lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow/blob/a01537a9328b085075866a965191d6323ad2cf7d/testassets/HelloWorldApp/Program.cs#L14&quot;&gt;Example here&lt;/a&gt; and &lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow/blob/a01537a9328b085075866a965191d6323ad2cf7d/testassets/HelloWorldApp/wwwroot/index.html#L11&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once your web content is running, the low-level way to communicate between JavaScript and .NET is using the APIs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.external.sendMessage&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;receiveMessage&lt;/code&gt; in JS (&lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow/blob/a01537a9328b085075866a965191d6323ad2cf7d/testassets/HelloWorldApp/wwwroot/index.html#L14-L20&quot;&gt;example&lt;/a&gt;) and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webWindowInstance.SendMessage&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webWindowInstance.OnWebMessageReceived&lt;/code&gt; in .NET - (&lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow/blob/a01537a9328b085075866a965191d6323ad2cf7d/testassets/HelloWorldApp/Program.cs#L21-L24&quot;&gt;example&lt;/a&gt;). However if you’re building a Blazor app, you don’t need to use these low-level APIs and can use Blazor’s regular JS interop feature instead.&lt;/p&gt;

&lt;h1 id=&quot;hosting-a-blazor-app&quot;&gt;Hosting a Blazor app&lt;/h1&gt;

&lt;p&gt;WebWindow isn’t coupled to Blazor. Here’s an &lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow/tree/master/samples/VueFileExplorer&quot;&gt;example of using Vue.js to render a simple directory-explorer app inside a WebWindow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But if you do want to use Blazor, that’s extremely clean and easy. I’ve also made a small add-on package, &lt;a href=&quot;https://www.nuget.org/packages/WebWindow.Blazor&quot;&gt;WebWindow.Blazor&lt;/a&gt;, that lets you host a Blazor app with one line in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Program.Main&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ComponentsDesktop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Startup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;My Blazor App&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;wwwroot/index.html&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To recap, this does &lt;em&gt;not&lt;/em&gt; involve WebAssembly, Node.js, or a privately-bundled copy of Chromium. It’s just .NET Core running natively, communicating directly with the OS’s own web rendering technology. The result, this time in macOS:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/18/blazor-macos.jpg&quot; alt=&quot;Blazor example on macOS&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The complete &lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow/tree/master/samples/BlazorDesktopApp&quot;&gt;WebWindow+Blazor sample is here&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;how-it-works&quot;&gt;How it works&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;On &lt;strong&gt;Windows&lt;/strong&gt;, WebWindow uses the new &lt;a href=&quot;https://docs.microsoft.com/en-us/microsoft-edge/hosting/webview2&quot;&gt;Chromium-based Edge via webview2&lt;/a&gt;, assuming you have that browser installed (it could fall back on older Edge if you don’t, but I haven’t implemented that)&lt;/li&gt;
  &lt;li&gt;On &lt;strong&gt;Mac&lt;/strong&gt;, it uses the OS’s built-in &lt;a href=&quot;https://developer.apple.com/documentation/webkit/wkwebview&quot;&gt;WKWebView&lt;/a&gt;, which is the same technology behind Safari&lt;/li&gt;
  &lt;li&gt;On &lt;strong&gt;Linux&lt;/strong&gt;, it uses &lt;a href=&quot;https://webkitgtk.org/&quot;&gt;WebKitGTK+2&lt;/a&gt;, which is yet again a WebKit-based technology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole point of this, compared with using Electron, is to produce apps that are smaller to download and use less memory. But does it actually? Here are the stats for download size:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/18/download-size-chart.png&quot; alt=&quot;Download size chart&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As you can see, whether you choose “standalone” (bundles a copy of the .NET Core runtime) or “framework-dependent” (rely on .NET Core being installed in the target OS) makes a vast difference to the resulting app size. Framework-dependent WebWindow apps can be truly tiny, since they only contain your own app’s binaries and aren’t bundling either a runtime or a browser.&lt;/p&gt;

&lt;p&gt;And now, stats for memory use:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/18/memory-use-chart.png&quot; alt=&quot;Memory use chart&quot; style=&quot;max-width:100%;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On Windows, WebWindow and Electron are using the same browser technology (Chromium), which eats up most of the memory. That explains why the difference between them isn’t huge. On Linux and Mac, the difference between using a self-bundled browser and the OS’s built in technology is more substantial.&lt;/p&gt;

&lt;h1 id=&quot;will-this-be-supported-and-maintained&quot;&gt;Will this be supported and maintained?&lt;/h1&gt;

&lt;p&gt;Currently I’m not making any promises! It’s best to think of it as yet another experiment for now. It’s possible that if enough other people want to get involved, it would be possible to create a proper open-source community project.&lt;/p&gt;

&lt;p&gt;What’s most urgently needed is someone with C++ experience to come and rewrite my prototype-quality C++ and Objective-C code the way it actually should be done. The chance that I’ve got all the memory management right here is close to zero. Maybe it should use CMake or another sane build config system too. (Note: it does have a &lt;a href=&quot;https://dev.azure.com/SteveSandersonMS/WebWindow/_build?definitionId=2&quot;&gt;cross-platform CI build on Azure DevOps&lt;/a&gt; though.)&lt;/p&gt;

&lt;p&gt;There’s also a large number of features you’d really want to add if you intended to use this in production. For example, the ability to set an app icon, to add a native menu bar, and so on. If you’re interested in contributing such functionality and will make it work cross-platform, please &lt;a href=&quot;https://github.com/SteveSandersonMS/WebWindow&quot;&gt;head over to the repo&lt;/a&gt;!&lt;/p&gt;
</description>
        <pubDate>Mon, 18 Nov 2019 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2019/11/18/2019-11-18-webwindow-a-cross-platform-webview-for-dotnet-core/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2019/11/18/2019-11-18-webwindow-a-cross-platform-webview-for-dotnet-core/</guid>
      </item>
    
      <item>
        <title>Exploring lighter alternatives to Electron for hosting a Blazor desktop app</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://electronjs.org/&quot;&gt;Electron&lt;/a&gt; was first open-sourced in 2014, and gained immediate popularity as a way to build desktop apps using web technologies (HTML+CSS+JS). At the core of its design is the idea of bundling a predictable environment:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;It bundles its own copy of Chromium&lt;/strong&gt;, so you know for sure how your HTML/CSS will be rendered and don’t have to worry about random old versions of IE (etc.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It bundles its own copy of Node.js&lt;/strong&gt;, so you have a known version of a portable programming platform that goes beyond the browser sandbox and can interact with the host OS directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These choices offered a lot of value five years ago, but in late 2019 you might choose differently. And these choices are also key to why Electron has a reputation for being unusually resource-hungry. A default blank Electron 8.0.0 application is 164MB to download (66MB compressed), and runs as 4 separate processes consuming 150MB RAM in total.&lt;/p&gt;

&lt;p&gt;It’s completely possible that these numbers look fine to you and are satisfactory for your scenario. If that’s the case, good for you! This post is not an attempt to bash Electron, which is a well-run project that people are clearly using successfully. In this post, I simply want to consider what other options we might have.&lt;/p&gt;

&lt;h2 id=&quot;how-light-could-it-get&quot;&gt;How light could it get?&lt;/h2&gt;

&lt;p&gt;What if…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;…we didn’t bundle Chromium&lt;/strong&gt;, but instead used whatever webview already exists in the OS? In 2019, just about any desktop OS is going to have a sufficiently modern, usually Chromium-based browser, ready to go. For your app, it likely doesn’t matter whether it’s Chromium from last week or last year.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;…we didn’t bundle Node&lt;/strong&gt;, but instead made use of the programming environment already in the OS, or optionally brought a different one? A framework-dependent .NET Core app can easily be under 1MB, and a standalone one (i.e., bundling its own copy of .NET Core) can get down to ~20MB linked and compressed.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Various Electron-lite alternative projects have already sprung up &lt;a href=&quot;#footnote-existing-electron-alternatives&quot;&gt;[1]&lt;/a&gt;. Of course, there are also PWAs, but that’s not what this post is about, since PWAs don’t have native access to the underlying OS.&lt;/p&gt;

&lt;h2 id=&quot;blazor-on-electron&quot;&gt;Blazor on Electron&lt;/h2&gt;

&lt;p&gt;We’ve had a lot of interest in using Blazor to build cross-platform desktop apps. It’s not surprising: combining the performance and productivity of C#/.NET with the familiarity of HTML/CSS UI rendering is powerful and appealing.&lt;/p&gt;

&lt;p&gt;So, we published a &lt;a href=&quot;https://github.com/aspnet/AspLabs/tree/master/src/ComponentsElectron&quot;&gt;sample and an experimental package for hosting Blazor on Electron&lt;/a&gt;. The key innovation here is that it’s &lt;em&gt;not&lt;/em&gt; running on WebAssembly, but rather uses the normal cross-platform .NET Core runtime to achieve full native .NET performance and enable full access to the host OS without any browser sandbox limitations.&lt;/p&gt;

&lt;p&gt;You can try this out today. Just note that it’s only an “asplabs” project, as we haven’t yet made any commitment to ship and support this technology.&lt;/p&gt;

&lt;h2 id=&quot;blazor-on-pure-webview&quot;&gt;Blazor on pure webview&lt;/h2&gt;

&lt;p&gt;It’s not hard to imagine how a Blazor hybrid desktop app could be slimmed down &lt;em&gt;dramatically&lt;/em&gt; further. We could swap out Electron for a pure OS-native web view, reasoning that in 2019, there’s virtually always a good enough one available on your target machine. Plus we don’t really need Node as a cross-platform programming environment, since .NET Core already plays that role for us.&lt;/p&gt;

&lt;p&gt;To verify this, I built &lt;a href=&quot;https://github.com/steveSandersonMS/BlazorDesktop&quot;&gt;an experiment called BlazorDesktop&lt;/a&gt;. This is very similar to &lt;a href=&quot;https://github.com/aspnet/AspLabs/tree/master/src/ComponentsElectron&quot;&gt;BlazorElectron&lt;/a&gt;, and in fact most of the code is a copy-paste from it. Again, it runs on native .NET Core (so &lt;em&gt;not&lt;/em&gt; on WebAssembly), but now it’s running on a much smaller rendering stack, without any bundled Chromium or Node.js.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/01/blazor-desktop.png&quot; alt=&quot;Blazor Desktop&quot; style=&quot;max-width:100%; border: 1px solid gray;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s a fully-functional Blazor application into which you can add any native .NET Core-based functionality, and runs as an extremely lightweight desktop app - see below for numbers. Unlike a PWA, it’s &lt;em&gt;not&lt;/em&gt; limited to the browser sandbox.&lt;/p&gt;

&lt;p&gt;If you’re interested in &lt;a href=&quot;https://github.com/steveSandersonMS/BlazorDesktop&quot;&gt;trying this out&lt;/a&gt;, please note that it’s purely a quick proof-of-concept, and currently is Windows only. Expanding it to be cross-platform wouldn’t be too hard (I’d use something like &lt;a href=&quot;https://github.com/zserge/webview&quot;&gt;webview&lt;/a&gt; to add Mac+Linux support) but is not something I’m actively doing right now. Send a PR if you’re interested.&lt;/p&gt;

&lt;h2 id=&quot;the-stats&quot;&gt;The stats&lt;/h2&gt;

&lt;p&gt;Not surprisingly, this minimal Blazor + webview application is significantly smaller and less memory-hungry than one built on the whole Chromium + Node stack:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/01/download-size-chart.png&quot; alt=&quot;Download size comparison&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/11/01/memory-use-chart.png&quot; alt=&quot;Memory use comparison&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One of the neat things about .NET Core apps is that, with a simple switch, you can control whether the publish output bundles its own copy of the .NET Core runtime (a.k.a. &lt;em&gt;standalone&lt;/em&gt;), or whether it assumes the runtime will already be installed on the target OS (a.k.a. &lt;em&gt;framework-dependent&lt;/em&gt;). This is captured in the first graph above, and you can see it makes a huge difference to the output size, since the Blazor libraries themselves are very compact.&lt;/p&gt;

&lt;p&gt;In corporate environments where you know that certain software is already installed, you can safely distribute tiny &amp;lt; 1MB framework-dependent apps, where the same binaries run on any supported OS. But for public distribution, you’d most likely publish a standalone app - generating different binaries for each of Windows, Mac, and Linux users.&lt;/p&gt;

&lt;p&gt;Note that about 200KB of the compressed Blazor webview app above consists of Bootstrap styling, so you could drop that if you’re using something else.&lt;/p&gt;

&lt;h2 id=&quot;what-would-have-to-be-built-to-make-this-viable&quot;&gt;What would have to be built to make this viable&lt;/h2&gt;

&lt;p&gt;As I’ve said, Blazor Desktop is currently just a quick proof-of-concept, built entirely during the waking part of my return journey from &lt;a href=&quot;https://ndcsydney.com/&quot;&gt;NDC Sydney&lt;/a&gt;. It would have a long way to go to turn into a viable product.&lt;/p&gt;

&lt;p&gt;It would need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Cross-platform support&lt;/strong&gt;, e.g., using &lt;a href=&quot;https://github.com/zserge/webview&quot;&gt;webview&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Edge (Chromium) support&lt;/strong&gt;, so that on Windows 10 we’re using the OS’s own Chromium-based browser instead of the Edge-based webview used in my prototype. This would also enable access to Chrome-style browser dev tools automatically.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Handling the scenario where a suitable webview can’t be found&lt;/strong&gt;. In the rare case where a user’s OS doesn’t supply an acceptable webview technology, we could prompt the user and download a standalone Chromium distribution (likely via &lt;a href=&quot;https://bitbucket.org/chromiumembedded/cef/src&quot;&gt;CEF&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Desktop APIs&lt;/strong&gt; - this is the big one that would be expensive to achieve from scratch. Electron doesn’t just use Node as a general-purpose programming environment; it also ships a set of cross-platform APIs for interacting with the desktop OS for tasks like copying to clipboard, changing the taskbar/dock icon, displaying native dropdown menus, showing native prompts/dialogs, and many more such things. .NET Core itself supplies a huge proportion of the APIs that applications need, but today it doesn’t address many of the desktop-focused ones, since there isn’t today any standard cross-platform .NET Core-based UI technology. Any realistic app needs these capabilities. Conceivably it might be worth bundling Node just to get cross-platform implementations for these APIs (though still without bundling Chromium).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Publishing and downloading updates automatically&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;feedback-requested&quot;&gt;Feedback requested&lt;/h2&gt;

&lt;p&gt;My reason for writing this is mainly to learn more about how the developer community feels about these technologies. Do you have scenarios for building hybrid desktop apps with .NET+HTML+CSS? Would you be happy to use Blazor with Electron, or do you feel it’s necessary to have something more bare-metal?&lt;/p&gt;

&lt;h2 id=&quot;footnote-existing-electron-alternatives&quot;&gt;Footnote: existing Electron alternatives&lt;/h2&gt;

&lt;p&gt;Many people have thought about building lighter alternatives to Electron over the years. Various open-source projects now exist, though it’s not clear that any really have the critical momentum for mass adoption. Some of them, like &lt;a href=&quot;https://github.com/chromelyapps/Chromely&quot;&gt;Chromely&lt;/a&gt;, eliminate Node and only bundle Chromium. Others, like &lt;a href=&quot;https://github.com/neutralinojs/neutralinojs&quot;&gt;Neutralino&lt;/a&gt;, eliminate Chromium and only bundle a Node-based programming model combined with the host OS’s browser technology. At the ultra-minimal end, &lt;a href=&quot;https://github.com/zserge/webview&quot;&gt;webview&lt;/a&gt; is simply an abstraction over the idea of a webview: it shows your HTML/CSS in whatever browser technology is built into the host OS, and doesn’t provide any cross-platform programming model of its own.&lt;/p&gt;
</description>
        <pubDate>Fri, 01 Nov 2019 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2019/11/01/exploring-lighter-alternatives-to-electron-for-hosting-a-blazor-desktop-app/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2019/11/01/exploring-lighter-alternatives-to-electron-for-hosting-a-blazor-desktop-app/</guid>
      </item>
    
      <item>
        <title>File uploads with Blazor</title>
        <description>&lt;p&gt;For a long time we’ve expected that we’d add a built-in “file input” feature to Blazor. This would let users pick local files and supply them to your application. Possible uses cases include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;You want to upload and store those files on a server&lt;/li&gt;
  &lt;li&gt;Or, you want to read and import some data from them&lt;/li&gt;
  &lt;li&gt;Or, you want to present an editor UI for the file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It applies equally to client-side or server-side Blazor. In client-side Blazor, you’re loading the file into the .NET application’s memory, which can then edit it locally or could make an HTTP request to transfer it to some backend server. In server-side Blazor, your code is already running on the server, but you still want to be able to read files picked by the user.&lt;/p&gt;

&lt;h2 id=&quot;existing-options&quot;&gt;Existing options&lt;/h2&gt;

&lt;p&gt;There are already several open-source or third-party libraries in this area (&lt;a href=&quot;https://remibou.github.io/Upload-file-with-Blazor/&quot;&gt;example from Rémi Bourgarel&lt;/a&gt;, &lt;a href=&quot;https://www.syncfusion.com/blazor-components/blazor-file-upload&quot;&gt;example from SyncFusion&lt;/a&gt;). It’s especially worth mentioning &lt;a href=&quot;https://github.com/Tewr/BlazorFileReader&quot;&gt;Tewr’s BlazorFileReader library&lt;/a&gt;, which does an excellent job and is quite similar to what I’m proposing in this post.&lt;/p&gt;

&lt;p&gt;What I want out of a great file input component is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Does not require setting up a separate server-side API endpoint&lt;/strong&gt;. The file data needs to get into Blazor via the existing JS interop mechanism.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Provides access to the file data as a regular .NET &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&lt;/code&gt;&lt;/strong&gt;, so other code can handle it just the same as if it were a normal file on disk.
    &lt;ul&gt;
      &lt;li&gt;This must literally stream the content into the .NET process, since we don’t want to depend on loading it all into memory at once.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Works independently of SignalR message size limits&lt;/strong&gt; and &lt;strong&gt;file API buffer sizes&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;This is what several of the existing options don’t manage. By default, SignalR imposes a limit of 32KB for incoming messages, and .NET APIs like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream.CopyToAsync&lt;/code&gt; use much larger internal buffers, so the streaming logic needs to work with this and not require reconfiguration.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;… while achieving &lt;strong&gt;near-native-HTTP transfer speeds&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;This is the hardest bit, not addressed by existing solutions as far as I know. There are simple ways to satisfy all the requirements above if you’re willing to accept greatly reduced upload rates. I’ll talk about the challenges and solutions later in this post.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;introducing-inputfile&quot;&gt;Introducing &amp;lt;InputFile&amp;gt;&lt;/h2&gt;

&lt;p&gt;As a possible starting point for a future built-in feature, I’ve published a &lt;a href=&quot;https://www.nuget.org/packages/BlazorInputFile&quot;&gt;NuGet package called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BlazorInputFile&lt;/code&gt;&lt;/a&gt; (&lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorInputFile&quot;&gt;source on GitHub&lt;/a&gt;), which provides a component called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputFile&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its features include &lt;strong&gt;uploading a single file&lt;/strong&gt; (&lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorInputFile/blob/master/samples/Sample.Core/Pages/SingleFile.razor&quot;&gt;sample source&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/09/13/single-file.gif&quot; alt=&quot;Single file upload&quot; style=&quot;max-width:100%; border: 1px solid gray;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Or, &lt;strong&gt;multi-file upload&lt;/strong&gt; and &lt;strong&gt;progress notifications&lt;/strong&gt; (&lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorInputFile/blob/master/samples/Sample.Core/Pages/MultiFile.razor&quot;&gt;sample source&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/09/13/multi-file.gif&quot; alt=&quot;Multiple file upload&quot; style=&quot;max-width:100%; border: 1px solid gray;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Or, &lt;strong&gt;custom UI&lt;/strong&gt; including &lt;strong&gt;drag-drop support&lt;/strong&gt; (&lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorInputFile/blob/master/samples/Sample.Core/Pages/DragDropViewer.razor&quot;&gt;sample source&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/09/13/dragdrop.gif&quot; alt=&quot;Drag-drop viewer&quot; style=&quot;max-width:100%; border: 1px solid gray;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;First, be sure you’re on the latest &lt;a href=&quot;https://devblogs.microsoft.com/aspnet/asp-net-core-and-blazor-updates-in-net-core-3-0-preview-9/&quot;&gt;3.0.0-preview9 version&lt;/a&gt; of Blazor, or newer if you’re reading this from the future. You can use either server-side or client-side (WebAssembly).&lt;/p&gt;

&lt;p&gt;Add a dependency on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BlazorInputFile&lt;/code&gt; package in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;BlazorInputFile&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0.1.0-preview&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;for-server-side-blazor&quot;&gt;For server-side Blazor&lt;/h3&gt;

&lt;p&gt;Reference the package’s JavaScript file by editing your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_Host.cshtml&lt;/code&gt; to add the following. It can go in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;body&amp;gt;&lt;/code&gt; or in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, wherever you want:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;_content/BlazorInputFile/inputfile.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_Imports.razor&lt;/code&gt;, add:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@using BlazorInputFile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Temporary caveat:&lt;/em&gt; Until .NET Core 3.0 ships, there’s &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorInputFile/commit/309b2076869f8f97c3f8b6c6bc8e34318df16bf5&quot;&gt;a bug you need to work around&lt;/a&gt; if you’re hosting on IIS Express. Thanks to Tewr for &lt;a href=&quot;https://github.com/aspnet/AspNetCore/issues/13470&quot;&gt;originally reporting this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That’s it - you’re ready to go! Move on to &lt;a href=&quot;#usage&quot;&gt;usage instructions&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;for-client-side-blazor&quot;&gt;For client-side Blazor&lt;/h3&gt;

&lt;p&gt;Due to &lt;a href=&quot;https://github.com/aspnet/AspNetCore/issues/13103&quot;&gt;a bug&lt;/a&gt; that we’ll fix before client-side Blazor is shipped, you can’t just reference &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inputfile.js&lt;/code&gt; from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_content&lt;/code&gt;. Instead you’ll have to manually copy &lt;a href=&quot;https://raw.githubusercontent.com/SteveSandersonMS/BlazorInputFile/master/BlazorInputFile/wwwroot/inputfile.js&quot;&gt;the contents of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;inputfile.js&lt;/code&gt; from GitHub&lt;/a&gt; into a file in your project. For example, put it directly into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wwwroot&lt;/code&gt;, and then add the following reference into your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;src=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;inputfile.js&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This manual file-copying step for client-side Blazor will be fixed and eliminated soon. Finally, in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_Imports.razor&lt;/code&gt;, add:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@using BlazorInputFile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;

&lt;p&gt;In one of your components, you can now add an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputFile&amp;gt;&lt;/code&gt; component. You’ll also want to add an event handler for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OnChange&lt;/code&gt; so you can respond when files are picked:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;InputFile&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;OnChange=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HandleFileSelected&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

@code {
    void HandleFileSelected(IFileListEntry[] files)
    {
        // Do something with the files, e.g., read them
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this case, we’re only allowing single-file selection, so the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;files&lt;/code&gt; array will have either zero or one entry. You can read metadata about the file even before you actually transfer the contents of the file anywhere:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;InputFile&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;OnChange=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HandleFileSelected&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

@if (file != null)
{
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Name: @file.Name&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Size in bytes: @file.Size&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Last modified date: @file.LastModified.ToShortDateString()&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Content type (not always supplied by the browser): @file.Type&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}

@code {
    IFileListEntry file;

    void HandleFileSelected(IFileListEntry[] files)
    {
        file = files.FirstOrDefault();
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can read data from the file either immediately on selection, or later (e.g., when the user clicks an ‘upload’ button). To read the data, just access &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;file.Data&lt;/code&gt; which is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Stream&lt;/code&gt;. For example, to count the number of lines in a text file:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;InputFile&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;OnChange=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HandleFileSelected&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

@if (file != null)
{
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Number of lines read: @numLines&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CountLines&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Count&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
}

@code {
    int numLines;
    IFileListEntry file;

    void HandleFileSelected(IFileListEntry[] files)
    {
        file = files.FirstOrDefault();
    }

    async Task CountLines()
    {
        numLines = 0;
        using (var reader = new System.IO.StreamReader(file.Data))
        {
            while (await reader.ReadLineAsync() != null)
            {
                numLines++;
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You can &lt;strong&gt;only&lt;/strong&gt; use asynchronous APIs on this stream (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadLineAsync&lt;/code&gt;, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ReadLine&lt;/code&gt;), because the data has to be transferred over the network.&lt;/p&gt;

&lt;p&gt;If you want to support multiple file selection, just add a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;multiple&lt;/code&gt; attribute:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;InputFile&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;multiple&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;OnChange=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;HandleFileSelected&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and now your event handler will receive a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IFileListEntry[]&lt;/code&gt; that can contain multiple entries.&lt;/p&gt;

&lt;h2 id=&quot;implementation-notes&quot;&gt;Implementation notes&lt;/h2&gt;

&lt;p&gt;For client-side Blazor (i.e., on WebAssembly), the data transfer between the browser’s JavaScript APIs and .NET is very simple and near-instant, since it’s all running locally. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputFile&amp;gt;&lt;/code&gt; uses Blazor’s low-level unmarshalled interop APIs to copy the requested chunks of the binary data directly into .NET memory without needing any serialization.&lt;/p&gt;

&lt;p&gt;For server-side Blazor (i.e., via SignalR), there’s a lot more going on. We have to fetch chunks via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IJSRuntime&lt;/code&gt; which only allows JSON-serializable data, and imposes a limit on the size of each returned chunk. By default, SignalR’s maximum message size is 32KB.&lt;/p&gt;

&lt;h3 id=&quot;bandwidth-latency-and-security&quot;&gt;Bandwidth, latency, and security&lt;/h3&gt;

&lt;p&gt;Other libraries approaching this problem have required users to configure a SignalR message size greater than whatever maximum buffer size is used by the I/O APIs you’re using (e.g., the default for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CopyToAsync&lt;/code&gt; is about 128KB). As well as being inconvenient for developers, this still leaves a serious performance issue. Since each chunk is being fetched sequentially, the maximum transfer rate becomes limited not only by network bandwidth, but also by network latency. If your round-trip ping time from client to server is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L&lt;/code&gt; (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L = 0.2 sec&lt;/code&gt;), and your SignalR message size is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S&lt;/code&gt; (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S = 32KB&lt;/code&gt; by default), then the tranfer rate cannot exceed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S/L&lt;/code&gt; (in this example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;32/0.2 = 160 KB/sec&lt;/code&gt;), even if you have a terrabit network connection.&lt;/p&gt;

&lt;p&gt;Additionally, even if your SignalR message size is arbitrarily large, the I/O APIs you’re using might themselves use smaller buffers. I often see code that reads data from streams in 1 KB chunks. For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;L=0.2&lt;/code&gt;, that would result in a maximum transfer rate of just 5KB/sec! It’s not enough just to &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.io.bufferedstream&quot;&gt;wrap a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BufferedStream&lt;/code&gt;&lt;/a&gt; around the source, since then you wouldn’t get progress notifications as often.&lt;/p&gt;

&lt;p&gt;My goal here is to get much closer to using your full network bandwidth. The approach this library uses is parallelism: the .NET side sets up a data structure that can hold many chunks (default total size is around 1MB), and asks the JS side to populate segments within it via a lot of concurrent interop calls. The parallelism amortises the latency, so the bottleneck ends up being your actual network bandwidth, which is what we want. But to maximise UI responsiveness, the I/O operations don’t wait for that whole ~1MB structure to be filled - they receive completion notifications as each smaller segment comes in from the JS side.&lt;/p&gt;

&lt;p&gt;Also, for security reasons, we don’t want to trust the JS-side code to send us as much data as it wants. That would let misbehaving clients occupy as much .NET memory as they want. So, all the transfer operations are initiated from the .NET side, and we can be sure to make full use of the allowed memory but no more.&lt;/p&gt;

&lt;p&gt;The net result of all this is that developers don’t have to reconfigure SignalR or specify custom buffer sizes when making file API calls. It all just works efficiently and safely by default.&lt;/p&gt;

&lt;h3 id=&quot;perf-numbers&quot;&gt;Perf numbers&lt;/h3&gt;

&lt;p&gt;For client-side Blazor (WebAssembly), the data transfer speed is limited only by your CPU. You can load a 10 MB file into .NET memory almost instantly. On my machine, loading a 100 MB file into a .NET &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte&lt;/code&gt; array takes about a second.&lt;/p&gt;

&lt;p&gt;To test with server-side Blazor, I deployed an application to a server about 4000 miles from me (so there’s plenty of latency), and tried uploading a 20MB file.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;With a plain native HTTP upload on a fast office connection, the upload time was around 9 to 10 seconds. On the same network, transferring via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputFile&amp;gt;&lt;/code&gt; took around 12 to 14 seconds.&lt;/li&gt;
  &lt;li&gt;For the same 20MB file but over a slower cafe wifi connection, the native HTTP upload times were between 21 and 26 seconds. On the same network, transferring via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputFile&amp;gt;&lt;/code&gt; took between 23 and 27 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s entirely expected that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputFile&amp;gt;&lt;/code&gt; takes ~30% longer than native HTTP uploads, because it has to base64 encode the data since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IJSRuntime&lt;/code&gt; only allows JSON responses. For smaller files, e.g., under 5MB, it’s unlikely that users will perceive any difference, and the vast level of extra convenience offered by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputFile&amp;gt;&lt;/code&gt; (e.g., no need to set up a separate API endpoint) makes this well worthwhile. We’ll also be able to eliminate the base64 penalty in the future by adding support for binary responses to JS interop calls, or expose SignalR’s built-in streaming mechanisms, at which point there should be no meaningful speed difference versus native HTTP uploads.&lt;/p&gt;
</description>
        <pubDate>Fri, 13 Sep 2019 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2019/09/13/blazor-inputfile/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2019/09/13/blazor-inputfile/</guid>
      </item>
    
      <item>
        <title>Integrating FluentValidation with Blazor</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://fluentvalidation.net/&quot;&gt;FluentValidation&lt;/a&gt; is a popular validation library for .NET Core by &lt;a href=&quot;https://jeremyskinner.co.uk/&quot;&gt;Jeremy Skinner&lt;/a&gt;. It has some advantages over .NET Core’s built-in DataAnnotations validation system, such as a richer set of rules, easier configuration, and easier extensibility.&lt;/p&gt;

&lt;p&gt;Blazor ships with built-in support for forms and validation, but Blazor doesn’t know about FluentValidation, and FluentValidation doesn’t know about Blazor. So, how can we make them work nicely together?&lt;/p&gt;

&lt;h2 id=&quot;a-simple-validation-example&quot;&gt;A simple validation example&lt;/h2&gt;

&lt;p&gt;With FluentValidation, you define a &lt;em&gt;validator&lt;/em&gt; class for the model types you want to validate. For example, if you have this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Customer&lt;/code&gt; class:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Customer&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… then you might define a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CustomerValidator&lt;/code&gt; class as follows:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerValidator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AbstractValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomerValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MaximumLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MaximumLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;More interesting rules are possible, but let’s start with this. Now say you want to have a UI for creating &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Customer&lt;/code&gt; instances in Blazor. You could use the following in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.razor&lt;/code&gt; component:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;EditForm&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Model=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;OnValidSubmit=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SaveCustomer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;FluentValidator&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;TValidator=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;CustomerValidator&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Your name&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;First name&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer.FirstName&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Last name&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer.LastName&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ValidationMessage&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;For=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; customer.FirstName)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;ValidationMessage&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;For=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; customer.LastName)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/EditForm&amp;gt;&lt;/span&gt;

@code {
    private Customer customer = new Customer();

    void SaveCustomer()
    {
        Console.WriteLine(&quot;TODO: Actually do something with the valid data&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This uses Blazor’s built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EditForm&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InputText&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ValidationMessage&lt;/code&gt; components to track the state of the editing process and display any validation error messages. Here’s how this looks:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/09/04/simple-validation.gif&quot; alt=&quot;Simple valdiation&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;h4 id=&quot;how-it-works&quot;&gt;How it works&lt;/h4&gt;

&lt;p&gt;So we’ve got FluentValidation rules working with Blazor – but how? The answer is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;FluentValidator&amp;gt;&lt;/code&gt; component. This is &lt;em&gt;not&lt;/em&gt; built-in to Blazor, but rather is a quick example I’ve made to show how you can do this integration. The code and explanation for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;FluentValidator&amp;gt;&lt;/code&gt; is later in this blog post.&lt;/p&gt;

&lt;h3 id=&quot;validating-child-objects&quot;&gt;Validating child objects&lt;/h3&gt;

&lt;p&gt;What if each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Customer&lt;/code&gt; also has an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Address&lt;/code&gt;, and we want to validate the properties on that child object? For example, update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Customer&lt;/code&gt; class to:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Customer&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Address&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Address&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Line1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;City&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Postcode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and update the validation class to:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerValidator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AbstractValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CustomerValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MaximumLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MaximumLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SetValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AddressValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AddressValidator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AbstractValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AddressValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Line1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;City&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;address&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;address&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Postcode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;MaximumLength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, FluentValidation uses the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SetValidator&lt;/code&gt; API to reference one validator class from another. To create a UI for this, you could update your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;EditForm&amp;gt;&lt;/code&gt; to:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;EditForm&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Model=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;OnValidSubmit=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;SaveCustomer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- Leave the rest here unchanged --&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Your address&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Line 1&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer.Address.Line1&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ValidationMessage&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;For=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; customer.Address.Line1)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;City&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer.Address.City&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ValidationMessage&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;For=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; customer.Address.City)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Postcode&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;customer.Address.Postcode&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ValidationMessage&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;For=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; customer.Address.Postcode)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/EditForm&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and you get:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/09/04/child-object-validation.gif&quot; alt=&quot;Child object validation&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;validating-collections&quot;&gt;Validating collections&lt;/h3&gt;

&lt;p&gt;Let’s consider a more advanced scenario. Each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Customer&lt;/code&gt; has a set of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PaymentMethod&lt;/code&gt; objects. They must have at least one. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PaymentMethod&lt;/code&gt; instances can be of different types, and the validation rules in effect depend on the type. To represent all this, you could add the following property to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Customer&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PaymentMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PaymentMethods&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PaymentMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and define &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PaymentMethod&lt;/code&gt; with:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PaymentMethod&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;enum&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CreditCard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HonourSystem&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MethodType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CardNumber&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To configure the validation rules, update &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CustomerValidator&lt;/code&gt;’s constructor to add:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PaymentMethods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;You have to define at least one payment method&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;RuleForEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PaymentMethods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SetValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PaymentMethodValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PaymentMethodValidator&lt;/code&gt; defined as:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PaymentMethodValidator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;AbstractValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PaymentMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PaymentMethodValidator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;RuleFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;card&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;card&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CardNumber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;CreditCard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;When&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MethodType&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PaymentMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CreditCard&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RuleForEach&lt;/code&gt; applies to each entry in a collection, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;When&lt;/code&gt; lets you apply rules conditionally based on other properties. In this example, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CardNumber&lt;/code&gt; property has to be valid a credit card number, but only when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MethodType&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CreditCard&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Creating a list editor in Blazor is pretty simple. Generally you just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@foreach&lt;/code&gt; over each item in the list to display it, plus offer an “Add item” button and “Remove item” buttons. For example, add the following inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;EditForm&amp;gt;&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;
    Payment methods
    [&lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AddPaymentMethod&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Add new&lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;]
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;ValidationMessage&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;For=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; customer.PaymentMethods)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

@foreach (var paymentMethod in customer.PaymentMethods)
{
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputSelect&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;paymentMethod.MethodType&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;option&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@PaymentMethod.Type.CreditCard&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Credit card&lt;span class=&quot;nt&quot;&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;option&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@PaymentMethod.Type.HonourSystem&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Honour system&lt;span class=&quot;nt&quot;&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/InputSelect&amp;gt;&lt;/span&gt;

        @if (paymentMethod.MethodType == PaymentMethod.Type.CreditCard)
        {
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Card number&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;paymentMethod.CardNumber&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        }
        else if (paymentMethod.MethodType == PaymentMethod.Type.HonourSystem)
        {
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;span&amp;gt;&lt;/span&gt;Sure, we trust you to pay us somehow eventually&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
        }

        &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; customer.PaymentMethods.Remove(paymentMethod))&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Remove&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ValidationMessage&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;For=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; paymentMethod.CardNumber)&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AddPaymentMethod&lt;/code&gt; should be declared inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@code&lt;/code&gt; block as follows:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AddPaymentMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PaymentMethods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;PaymentMethod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This lets users both add and remove payment methods, and choose a type for each one. Validation rules vary according to the type:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/09/04/collection-validation.gif&quot; alt=&quot;Collection validation&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you want the completed code for this sample, it’s &lt;a href=&quot;https://gist.github.com/SteveSandersonMS/090145d7511c5190f62a409752c60d00&quot;&gt;in this Gist&lt;/a&gt;. There you’ll also find the source for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FluentValidator&lt;/code&gt; component, which is also discussed more below.&lt;/p&gt;

&lt;p&gt;Note that this is only a prototype-level integration between Blazor and FluentValidation. There are some caveats discussed below. It’s not something I’m personally planning to extend further and offer support for, but perhaps someone who wants to use it might want to take it further and ship a NuGet package.&lt;/p&gt;

&lt;h2 id=&quot;blazors-forms-and-validation-extensibility&quot;&gt;Blazor’s forms and validation extensibility&lt;/h2&gt;

&lt;p&gt;Blazor ships with built-in support for forms and validation. These concepts aren’t welded to the core of Blazor itself, but rather live in an optional package called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.AspNetCore.Components.Forms&lt;/code&gt;. The intention is that if you don’t like any aspect of how this works, you can replace it – either that entire package, or just parts of it.&lt;/p&gt;

&lt;p&gt;That optional &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Forms&lt;/code&gt; package contains two main pieces of functionality:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A system for tracking edits within a form and associated validation messages. This is implemented as components like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;EditForm&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputText&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;InputSelect&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;ValidationSummary&amp;gt;&lt;/code&gt;, and so on. This isn’t itself tied to any particular validation or metadata framework.&lt;/li&gt;
  &lt;li&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;DataAnnotationsValidator&amp;gt;&lt;/code&gt; component, which provides integration with .NET Core’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.ComponentModel.DataAnnotations&lt;/code&gt; library (which &lt;em&gt;is&lt;/em&gt; a specific validation and metadata framework).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The example in this blog post continues using items from #1 but completely replaces #2. That is, instead of having &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;DataAnnotationsValidator&amp;gt;&lt;/code&gt; inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;EditForm&amp;gt;&lt;/code&gt;, we created and used a new thing called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;FluentValidator&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What a custom validator component such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;FluentValidator&amp;gt;&lt;/code&gt; needs to do is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Receive an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EditContext&lt;/code&gt; as a cascading parameter&lt;/li&gt;
  &lt;li&gt;Hook into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EditContext&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OnFieldChanged&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OnValidationRequested&lt;/code&gt; events so it knows when something is happening in the UI&lt;/li&gt;
  &lt;li&gt;Add or remove validation messages in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ValidationMessageStore&lt;/code&gt; whenever it wants. There’s no prescribed timing or lifecycle for this, so you can use literally any flow you want, e.g., for asynchronous validation or whatever else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href=&quot;https://gist.github.com/SteveSandersonMS/090145d7511c5190f62a409752c60d00#file-fluentvalidator-cs&quot;&gt;implementation I made for FluentValidator&lt;/a&gt; does exactly this.&lt;/p&gt;

&lt;h3 id=&quot;reflections-on-the-integration-code&quot;&gt;Reflections on the integration code&lt;/h3&gt;

&lt;p&gt;By far the most complex aspect of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;FluentValidator&amp;gt;&lt;/code&gt;’s logic is handling the difference between how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EditContext&lt;/code&gt; identifies fields and how FluentValidation does.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Blazor identifies fields using an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(object, propertyName)&lt;/code&gt; pair, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;object&lt;/code&gt; is an object reference and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;propertyName&lt;/code&gt; is a string&lt;/li&gt;
  &lt;li&gt;FluentValidation identifies fields using a property-chain string such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Address.Line1&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PaymentMethods[2].Expiry.Month&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason for Blazor’s approach is to support UI composition. Imagine you wanted to create an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;AddressEditor&amp;gt;&lt;/code&gt; component. It should be able to receive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Address&lt;/code&gt; instances from anywhere and edit them with validation. It would be a burden on the developer if they had to somehow pass in property-chain prefixes and combine strings to represent how to locate items within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;AddressEditor&amp;gt;&lt;/code&gt; form. It would be especially unpleasant in dynamic list scenarios. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(object, propertyName)&lt;/code&gt; system greatly simplifies this, because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AddressEditor&lt;/code&gt; can simply identify fields as something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(AddressInstance, &quot;Line1&quot;)&lt;/code&gt; without having to know anything about how the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AddressInstance&lt;/code&gt; is reached from some parent-level object(s). I’m sure there are advantages to FluentValidation’s approach too. Integrating these two frameworks means being able to translate between the two representations, so that’s what the &lt;a href=&quot;https://gist.github.com/SteveSandersonMS/090145d7511c5190f62a409752c60d00#file-fluentvalidator-cs-L42-L91&quot;&gt;majority of the complex logic is doing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Overall, the two biggest caveats with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;FluentValidator&amp;gt;&lt;/code&gt; logic I’ve provided here are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It doesn’t yet support validating individual fields. Instead, each time there’s an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OnFieldChanged&lt;/code&gt; event, it revalidates the entire object. This is inefficient plus you can see validation messages for fields you haven’t yet edited.
    &lt;ul&gt;
      &lt;li&gt;To fix this, we’d need some way to enumerate &lt;em&gt;all&lt;/em&gt; the validatable properties reachable from the root object. Then inside the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OnFieldChanged&lt;/code&gt; event, we’d take &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eventArgs.FieldIdentifier&lt;/code&gt; and translate that into a FluentValidation path chain string by checking which path string corresponds to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FieldIdentifier&lt;/code&gt;. Then we could tell FluentValidation only to validate that one property.&lt;/li&gt;
      &lt;li&gt;Alternatively, FluentValidation could offer another overload to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Validate&lt;/code&gt; API that takes a callback that returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt; for each field being considered to say whether or not it should be validated on this occasion.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;The async validation experience won’t be great as-is. From the FluentValidation docs, I see that if you have any async rules, then you’re not allowed to call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Validate&lt;/code&gt; any more, and must instead call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ValidateAsync&lt;/code&gt;. This returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Task&lt;/code&gt;, so you can’t get the validation results until all the async ones have completed.
    &lt;ul&gt;
      &lt;li&gt;To fix this, maybe FluentValidation could have an API that returns the synchronous results immediately, then a series of notifications as each of the async ones complete, so you can keep updating the UI.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s completely possible that FluentValidation already has all the integration points needed to do a better job of integrating than I did. If you know I missed something, please let me know!&lt;/p&gt;
</description>
        <pubDate>Wed, 04 Sep 2019 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2019/09/04/blazor-fluentvalidation/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2019/09/04/blazor-fluentvalidation/</guid>
      </item>
    
      <item>
        <title>Unit testing Blazor components - a prototype</title>
        <description>&lt;p&gt;One of our design goals for Blazor is to offer an absolutely first-rate testing system. Writing tests for your components should be natural, produtive, and satisfying. Those tests should run fast and reliably. We’ve have &lt;a href=&quot;https://github.com/aspnet/AspNetCore/issues/5458&quot;&gt;a placeholder issue&lt;/a&gt; for this, but it’s out of scope for the initial (.NET Core 3.0) release.&lt;/p&gt;

&lt;p&gt;So, I think it’s time to start clarifying how this can work.&lt;/p&gt;

&lt;h3 id=&quot;goals&quot;&gt;Goals&lt;/h3&gt;

&lt;p&gt;We do have a lot of experience writing tests for Blazor components. While building the framework itself, a substantial portion of the code we write is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Unit tests&lt;/strong&gt;, which directly execute methods on components and renderers, and make assertions about the resulting render batches and UI diffs inside them. These tests are extremely &lt;strong&gt;fast&lt;/strong&gt; and &lt;strong&gt;robust&lt;/strong&gt;, but unfortunately also extremely &lt;strong&gt;low-level&lt;/strong&gt;. You wouldn’t normally want to express your tests in terms of Blazor’s low-level internal UI diff description format.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;End-to-end (E2E) tests&lt;/strong&gt;, which use Selenium to control a headless browser instance and make assertions about the state of the browser DOM. These tests are &lt;strong&gt;high-level&lt;/strong&gt; (in that they talk about familiar concepts like HTML and actions like &lt;em&gt;clicking&lt;/em&gt;), but they are also very &lt;strong&gt;slow&lt;/strong&gt; (each one can take multiple seconds to run) and great care has to be taken to make them robust and not depend on the timings of async UI updates. It’s also not really possible to mock external services in these tests.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We think it will be possible for Blazor to include a set of test helpers that give you the benefits of both approaches, and the drawbacks of neither.&lt;/p&gt;

&lt;p&gt;The core idea is simply to provide unit test helpers that let you mount your components inside a “test renderer”. This will behave like browser automation, but &lt;em&gt;no actual browser will be involved&lt;/em&gt; (not even a headless one). It’s just pure .NET code, so runs with the speed and robustness of pure unit tests.&lt;/p&gt;

&lt;h3 id=&quot;a-prototype&quot;&gt;A prototype&lt;/h3&gt;

&lt;p&gt;To get the conversation going, I’ve put &lt;a href=&quot;https://github.com/SteveSandersonMS/BlazorUnitTestingPrototype&quot;&gt;a prototype here&lt;/a&gt;. Let’s take a look.&lt;/p&gt;

&lt;p&gt;Consider the classic “counter” example, in a file called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Counter.razor&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Counter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Current count: @currentCount&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;btn btn-primary&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;IncrementCount&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My prototype unit test package, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Microsoft.AspNetCore.Components.Testing&lt;/code&gt;, provides a class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestHost&lt;/code&gt; that lets you mount components and interact with them inside any traditional unit testing system (e.g., xUnit, NUnit). So if your unit test project has a reference to the application project, you can write tests as follows:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CounterTest&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TestHost&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestHost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CountStartsAtZero&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Current count: 0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CanIncrementCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Counter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;button&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Click&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Current count: 1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;p&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestHost&lt;/code&gt; is a way to render components under unit tests. You can find rendered elements in terms of CSS selectors, then you can either make assertions about them or trigger events on them. This is very much like traditional browser automation, but is implemented without using any actual browser.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TestHost&lt;/code&gt; also provides a way to supply DI services, such as mock instances, so you can describe how your component must behave when external services do certain things (e.g., when authentication or data access fails). As an example of that, consider the classic “weather forecasts” page (see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FetchData.razor&lt;/code&gt; in any brand-new Blazor project). This uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HttpClient&lt;/code&gt; to fetch and display data from an external source. You could write a unit test for that as follows:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FetchDataTest&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TestHost&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestHost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;DisplaysLoadingStateThenRendersSuppliedData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Set up a mock HttpClient that we'll be able to use to test arbitrary responses&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AddMockHttp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Capture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/sample-data/weather.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Initially shows loading state&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FetchData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Loading...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetMarkup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Now simulate a response from the HttpClient&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WaitForNextRender&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SetResult&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FetchData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WeatherForecast&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Summary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;First&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FetchData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WeatherForecast&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Summary&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Second&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}));&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Now we should be displaying the data&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;DoesNotContain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Loading...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetMarkup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FindAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tbody tr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;First&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OuterHtml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Second&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OuterHtml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;lets-do-some-tdd&quot;&gt;Let’s do some TDD&lt;/h3&gt;

&lt;p&gt;You probably know this, but to recap: Test-driven Development (TDD) is the process of writing software through tests. For each behavior your software should have, you first write a unit test (which fails, because your software doesn’t have that behavior yet), then you implement the behavior, and then you see that your test now passes.&lt;/p&gt;

&lt;p&gt;Whether or not TDD really works depends a lot on whether the type of software you’re writing is amenable to unit testing and whether the technologies you’re using give you good ways of isolating units of code and a fast enough feedback loop.&lt;/p&gt;

&lt;p&gt;My hope, then, is that Blazor can offer unit test helpers that give you what you need so that TDD is not only viable but even enjoyable, even when writing components with very detailed user interactions.&lt;/p&gt;

&lt;p&gt;As an experiment, pretend you’re building a “todo list” component in Blazor. It needs to let users type in items, add them to a list, and check them off when they are done. Pretty obvious stuff. You might start with the following &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TodoList.razor&lt;/code&gt; component:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;@page &quot;/todo&quot;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Todo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    @foreach (var item in items)
    {
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;@item.Text&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    }
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Type something...&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Add&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

@code {
    private List&lt;span class=&quot;nt&quot;&gt;&amp;lt;TodoItem&amp;gt;&lt;/span&gt; items = new List&lt;span class=&quot;nt&quot;&gt;&amp;lt;TodoItem&amp;gt;&lt;/span&gt;();

    class TodoItem
    {
        public string Text { get; set; }
        public bool IsDone { get; set; }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/29/todo-initial.png&quot; alt=&quot;Initial todo list&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Currently, it doesn’t do anything. Typing and clicking “Add” does nothing. Let’s write a unit test describing the behavior we want. This would go into a separate unit testing project that references the app project:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TodoListTest&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TestHost&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;TestHost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;InitiallyDisplaysNoItems&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TodoList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FindAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CanAddItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Arrange&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TodoList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Act&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;My super item&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Submit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Assert&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FindAll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;My super item&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;li&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Trim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you run this either on the command line with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet test&lt;/code&gt; or in VS’s built-in runner, you’ll see that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InitiallyDisplaysNoItems&lt;/code&gt; passes, but &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CanAddItem&lt;/code&gt; fails (because we haven’t yet implemented it):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/29/todo-can-add-fail.png&quot; alt=&quot;First test failure&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The actual reported failure comes from our call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.Change&lt;/code&gt; - it reports &lt;em&gt;The element does not have an event handler for the event ‘onchange’.&lt;/em&gt;. This makes sense because we’re trying to trigger “change” on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt;, but there’s nothing bound to that element.&lt;/p&gt;

&lt;p&gt;Let’s implement the adding behavior, by using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@bind&lt;/code&gt; to wire up a “change” listener on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input&lt;/code&gt;, plus an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@onsubmit&lt;/code&gt; listener on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;form&lt;/code&gt; that actually adds items:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onsubmit=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;AddItem&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Type something...&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;newItemText&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Add&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

@code {
    private string newItemText;
    private List&lt;span class=&quot;nt&quot;&gt;&amp;lt;TodoItem&amp;gt;&lt;/span&gt; items = new List&lt;span class=&quot;nt&quot;&gt;&amp;lt;TodoItem&amp;gt;&lt;/span&gt;();

    void AddItem()
    {
        items.Add(new TodoItem { Text = newItemText });
    }

    class TodoItem { /* Leave as before */ }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And now, the test passes:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/29/todo-can-add-pass.png&quot; alt=&quot;First test pass&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;OK, that’s good, but I just remembered something else: each time you submit an item, we should auto-clear out the textbox so you can type in the next one. To see whether this already happens, try updating the unit test to check the textbox becomes empty:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CanAddItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ... leave the existing code here, and just add the following ...&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Empty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetAttributeValue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Oops, no - now this test fails with &lt;em&gt;Assert.Empty() Failure&lt;/em&gt;. This shows we’re not clearing out the textbox after add. To implement this, update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AddItem&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;AddItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TodoItem&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Text&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newItemText&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;newItemText&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and now the test passes. We’ve implemented the basics of adding items:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/29/todo-adding.gif&quot; alt=&quot;Adding todo items&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now, what about having checkboxes to mark items as done, and counting the number of remaining items? We could express that in a unit test by saying there must be an element with CSS class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.remaining&lt;/code&gt; that shows the count, and that each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;li&lt;/code&gt; should contain a checkbox you can toggle:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fact&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ShowsCountOfRemainingItems&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Arrange: list with two items&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TodoList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Item 1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Submit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Item 2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;form&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Submit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Assert 1: shows initial 'remaining' count&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.remaining&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Act/Assert 2: updates count when items are checked&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;li:first-child input[type=checkbox]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Change&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;component&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.remaining&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, this initially fails, because there is no &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.remaining&lt;/code&gt; item. But we can implement the behavior by changing the markup in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TodoList.razor&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Todo (&lt;span class=&quot;nt&quot;&gt;&amp;lt;span&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;remaining&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;@items.Count(x =&amp;gt; !x.IsDone)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;)&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    @foreach (var item in items)
    {
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;checkbox&quot;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;item.IsDone&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
            @item.Text
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    }
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and now the test passes. Not surprisingly, if you run the app in an actual browser, you can also see it working:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/29/todo-toggling.gif&quot; alt=&quot;Toggling todo items&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;status&quot;&gt;Status&lt;/h3&gt;

&lt;p&gt;So far, this is just a prototype, and we don’t plan to ship anything of this kind in the 3.0 release this year. My expectation is that we’ll be looking for your feedback as this prototype evolves over time. Maybe the final result will look just like this, or maybe it will be unrecognisably different in the end.&lt;/p&gt;

&lt;p&gt;At some point I expect we will ship unit test helpers in a preview package so you can get started using them for real, and ultimately have them in the box with the 5.0 release in late 2020.&lt;/p&gt;
</description>
        <pubDate>Thu, 29 Aug 2019 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2019/08/29/blazor-unit-testing-prototype/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2019/08/29/blazor-unit-testing-prototype/</guid>
      </item>
    
      <item>
        <title>IndexedDB in Blazor</title>
        <description>&lt;p&gt;Almost all rich client-side web apps (SPAs) involve interacting with a data store. Normally, that data store is held on some server, and the browser-based app queries it by making HTTP calls to some API endpoint. Another option, though, is to store a database &lt;em&gt;client-side&lt;/em&gt; in the browser. The great benefit of doing so is that it permits completely instant querying, and can even work offline.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API&quot;&gt;IndexedDB&lt;/a&gt; has been around for ages, and remains the dominant way to put a client-side database in your SPA. It’s an indexed store of JSON objects, which lets you configure your own versioned data schema and perform efficient queries against the indexes you’ve defined. Naturally, it works both offline and online.&lt;/p&gt;

&lt;h2 id=&quot;using-indexeddb-with-blazor&quot;&gt;Using IndexedDB with Blazor&lt;/h2&gt;

&lt;p&gt;You &lt;em&gt;could&lt;/em&gt; use the native IndexedDB APIs through Blazor’s JS interop capability. But you’ll have a rough time, because - to put it kindly - the IndexedDB APIs are atrocious. IndexedDB came onto the scene before &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Promise&lt;/code&gt;, so it has an events-based asynchrony model, which is a disaster to work with.&lt;/p&gt;

&lt;p&gt;So, I was pretty intrigued when I heard about &lt;a href=&quot;https://github.com/Reshiru/Blazor.IndexedDB.Framework&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reshiru.Blazor.IndexedDB.Framework&lt;/code&gt;&lt;/a&gt;, a NuGet package described as:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;An easy way to interact with IndexedDB and make it feel like EFCore&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Taking the bizarre IndexedDB APIs and turning them into nice, idiomatic .NET ones? Let’s have a look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: Internally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reshiru.Blazor.IndexedDB.Framework&lt;/code&gt; is built on &lt;a href=&quot;https://www.nuget.org/packages/TG.Blazor.IndexedDB&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TG.Blazor.IndexedDB&lt;/code&gt;&lt;/a&gt; by William Tulloch, which surfaces the IndexedDB features in .NET. Reshiru’s package builds on this by adding an EF-like DB context API.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;p&gt;First, in your Blazor app’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt;, add a reference to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reshiru.Blazor.IndexedDB.Framework&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Include=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Reshiru.Blazor.IndexedDB.Framework&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0.1&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt;’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ConfigureServices&lt;/code&gt;, set up the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IIndexedDbFactory&lt;/code&gt;. As far as I know, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IndexedDbFactory&lt;/code&gt; doesn’t maintain any state so it’s safe to make it &lt;em&gt;singleton&lt;/em&gt;, but if in the future it becomes stateful, you’d want to use &lt;em&gt;scoped&lt;/em&gt;:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AddScoped&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IIndexedDbFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IndexedDbFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you’re ready to define your data schema. This package makes it dead easy, since as with EF, it’s just C# classes:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;MyApp.Data&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Represents the database&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExampleDb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IndexedDb&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ExampleDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IJSRuntime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jSRuntime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jSRuntime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// These are like tables. Declare as many of them as you want.&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IndexedSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;People&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Person&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Required&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Key]&lt;/code&gt; property will be populated automatically by the package using the auto-incrementing unique value given by the browser to each stored entity.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[Required]&lt;/code&gt; annotations aren’t used by the data store at all, but I want them so my edit form will have validation rules later.&lt;/p&gt;

&lt;h2 id=&quot;querying-for-data&quot;&gt;Querying for data&lt;/h2&gt;

&lt;p&gt;Next it’s time to build some UI that shows what’s in the DB and lets you add and remove items.&lt;/p&gt;

&lt;p&gt;To make the Reshiru IndexedDB APIs available in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.razor&lt;/code&gt; files, add the following to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_Imports.razor&lt;/code&gt; in the root folder of the app:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;@using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Blazor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IndexedDB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Framework&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;@using&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Or wherever your data classes are&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, at the top of some &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.razor&lt;/code&gt; component that will fetch and display data, inject the DB service:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;@inject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IIndexedDbFactory&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DbFactory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can now write a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@code&lt;/code&gt; block that will get access to the database via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DbFactory&lt;/code&gt; and fetch some data from it:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;@code&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;people&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OnInitAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DbFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ExampleDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;people&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;People&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that I’m using a C# &lt;em&gt;using declaration&lt;/em&gt;. This is a brand-new C# language feature, so to make this compile, it’s necessary to use the latest version of the compiler. Enable this by putting the following into a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/code&gt; in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.csproj&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;LangVersion&amp;gt;&lt;/span&gt;preview&lt;span class=&quot;nt&quot;&gt;&amp;lt;/LangVersion&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, we can add some Razor markup to display the contents of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;people&lt;/code&gt; list:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;People&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

@if (people != null)
{
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;table&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;table&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;th&amp;gt;&lt;/span&gt;ID&lt;span class=&quot;nt&quot;&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;th&amp;gt;&lt;/span&gt;First name&lt;span class=&quot;nt&quot;&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Last name&lt;span class=&quot;nt&quot;&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
            @foreach (var person in people)
            {
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@person.Id&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@person.FirstName&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                    &lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@person.LastName&lt;span class=&quot;nt&quot;&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
            }
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Looks good! Running the application now, you’ll see it fetch and display zero items, because of course the database is empty. But if you use the browser’s dev tools, you can see that the Reshiru IndexedDB NuGet package did in fact create the database matching our schema:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/03/devtools-emptydb.png&quot; alt=&quot;Empty DB schema&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;inserting-data&quot;&gt;Inserting data&lt;/h2&gt;

&lt;p&gt;Blank databases aren’t very interesting. Let’s make a form into which users can enter details for a new entity, and then insert it into the database. Here’s a very basic validated form using standard Blazor APIs, which I put into the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.razor&lt;/code&gt; file as above:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;fieldset&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;legend&amp;gt;&lt;/span&gt;Add new person&lt;span class=&quot;nt&quot;&gt;&amp;lt;/legend&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;EditForm&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Model=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@newPerson&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;OnValidSubmit=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@SaveNewPerson&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@newPerson.FirstName&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;First name...&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;InputText&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;bind-Value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@newPerson.LastName&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;placeholder=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Last name...&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Add&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

        &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&amp;lt;ValidationSummary&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;DataAnnotationsValidator&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/EditForm&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/fieldset&amp;gt;&lt;/span&gt;

@code {
    Person newPerson = new Person();

    async Task SaveNewPerson()
    {
        // TODO
    }

    // ... rest as before
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This displays as follows. As you can see, it enforces the validation rules defined on the model type:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/03/form.png&quot; alt=&quot;Basic form&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;All that remains is to put in some implementation for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SaveNewPerson&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SaveNewPerson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DbFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ExampleDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;People&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newPerson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SaveChanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Refresh list and reset the form&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;newPerson&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OnInitAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and that’s all there is to it. The user can now insert new records to their client-side database without any requests having to go to a server:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/03/adding-people.gif&quot; alt=&quot;Adding people&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In case you’re wondering, the browser’s dev tools also confirm that the data was saved:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/03/devtools-withpeople.png&quot; alt=&quot;IndexedDB with contents&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;deleting-data&quot;&gt;Deleting data&lt;/h2&gt;

&lt;p&gt;To complete this example, let’s put in some &lt;em&gt;Delete&lt;/em&gt; buttons. I added the following markup at the end of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; that gets rendered for each row:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;td&amp;gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;@(() =&amp;gt; DeletePerson(person))&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;Delete&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and added the following C# handler method to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@code&lt;/code&gt; block:&lt;/p&gt;

&lt;div class=&quot;language-cs highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;DeletePerson&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Person&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DbFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ExampleDb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;People&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;person&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SaveChanges&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;OnInitAsync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;a-bigger-example&quot;&gt;A bigger example&lt;/h2&gt;

&lt;p&gt;The most compelling benefits of client-side databases are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Instant querying (with no HTTP traffic per query)&lt;/li&gt;
  &lt;li&gt;Offline support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To experience this with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reshiru.Blazor.IndexedDB.Framework&lt;/code&gt; package, I built a very simple “recipe database” app that:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Fetches a database of recipes from the server&lt;/strong&gt;, and uses it to populate a client-side database. This is only fetched once, and remains stored in the browser.&lt;/li&gt;
  &lt;li&gt;Offers &lt;strong&gt;instant search-on-every-keystroke&lt;/strong&gt; for recipes in that database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It would be pretty trivial to make this &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Offline_Service_workers&quot;&gt;work offline using a service worker&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Likewise, it would be simple enough to make the app synchronize updates with changes coming from the server. Give each record a “modified date”, and have the SPA request all records modified since the last batch it received, and insert/update/delete those.&lt;/p&gt;

&lt;p&gt;Here’s how it looks:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2019/08/03/recipe-site.gif&quot; alt=&quot;Recipe app&quot; style=&quot;max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;/wp-content/uploads/2019/08/03/RecipeApp.zip&quot;&gt;source code is available here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;what-i-learned&quot;&gt;What I learned&lt;/h3&gt;

&lt;p&gt;After my quick experiment with the recipes app, I think there are some really great aspects of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Reshiru.Blazor.IndexedDB.Framework&lt;/code&gt;, and some aspects that make it not quite ready yet.&lt;/p&gt;

&lt;p&gt;On the positive side, the package does a fantastic job of keeping the APIs simple and familiar for .NET developers. It’s as easy as, or perhaps easier than, using Entity Framework. The data schema is inferred directly from your model classes, and all aspects of serialization are handled for you. I’m convinced it’s possible to create an experience on par with EF backed by IndexedDB in the browser.&lt;/p&gt;

&lt;p&gt;However, I suspect this package has a couple more steps to go before it’s really practical for use in a real application, both of which are already tracked in GitHub issues:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;It doesn’t yet have any foreign key (FK) support&lt;/strong&gt; (&lt;a href=&quot;https://github.com/Reshiru/Blazor.IndexedDB.Framework/issues/8&quot;&gt;issue #8&lt;/a&gt;)
    &lt;ul&gt;
      &lt;li&gt;Currently it’s limited to JSON-serializing your entire entities, without any native way to represent associations between them&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It can’t yet query against IndexedDB itself, but instead loads the entire DB into .NET memory&lt;/strong&gt; (fixing this is a &lt;a href=&quot;https://github.com/Reshiru/Blazor.IndexedDB.Framework#planned-features-or-optimizations&quot;&gt;planned feature&lt;/a&gt;)
    &lt;ul&gt;
      &lt;li&gt;It’s pretty slow right now, and one of the major reasons is that when you do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DbFactory.Create&lt;/code&gt;, it literally fetches &lt;em&gt;all&lt;/em&gt; the data and deserializes it into an in-memory collection. Querying is then done in .NET memory via normal LINQ operations. So currently, it’s not actually using the native indexes on the IndexedDB.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since both of these capabilities already exist in the underlying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TG.Blazor.IndexedDB&lt;/code&gt; package, it’s probably quite feasible for them to be surfaced through Reshiru’s DB context APIs.&lt;/p&gt;

&lt;p&gt;I’m greatly looking forwards to seeing this package mature, as it’s clearly showing that a great client-side database experience is possible on .NET on WebAssembly.&lt;/p&gt;
</description>
        <pubDate>Sat, 03 Aug 2019 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2019/08/03/blazor-indexeddb/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2019/08/03/blazor-indexeddb/</guid>
      </item>
    
      <item>
        <title>Blazor: a technical introduction</title>
        <description>&lt;p&gt;Today &lt;a href=&quot;https://blogs.msdn.microsoft.com/webdev/2018/02/06/blazor-experimental-project/&quot;&gt;the ASP.NET team announced that Blazor has moved into the ASP.NET organization&lt;/a&gt;, 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!&lt;/p&gt;

&lt;p&gt;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 &lt;a href=&quot;https://blogs.msdn.microsoft.com/webdev/2018/02/06/blazor-experimental-project/&quot;&gt;announcement post&lt;/a&gt; covers more about the intended use cases, timescales, and so on.&lt;/p&gt;

&lt;p&gt;In this blog post, I want to provide some deeper technical details for those interested in how it actually works.&lt;/p&gt;

&lt;h2 id=&quot;running-net-in-the-browser&quot;&gt;Running .NET in the browser&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;not&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;So how does that let us run .NET? Well, &lt;a href=&quot;http://www.mono-project.com/news/2017/08/09/hello-webassembly/&quot;&gt;the Mono team is adding support for WebAssembly&lt;/a&gt;. 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.&lt;/p&gt;

&lt;p&gt;Mono aims to run under WebAssembly in two modes: &lt;em&gt;interpreted&lt;/em&gt; and &lt;em&gt;AOT&lt;/em&gt;.&lt;/p&gt;

&lt;h3 id=&quot;interpreted-mode&quot;&gt;Interpreted mode&lt;/h3&gt;

&lt;p&gt;In &lt;em&gt;interpreted mode&lt;/em&gt;, 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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.dll&lt;/code&gt; files) built by the normal .NET compilation toolchain.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2018/02/06/interpreted-mode.png&quot; alt=&quot;Diagram showing interpreted mode&quot; style=&quot;width: 400px; max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3 id=&quot;ahead-of-time-aot-compiled-mode&quot;&gt;Ahead-of-time (AOT) compiled mode&lt;/h3&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2018/02/06/aot-mode.png&quot; alt=&quot;Diagram showing AOT mode&quot; style=&quot;width: 400px; max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is similar to how &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/framework/tools/ngen-exe-native-image-generator&quot;&gt;the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ngen&lt;/code&gt; tool&lt;/a&gt; has historically allowed AOT compilation of .NET binaries to native machine code, or more recently, &lt;a href=&quot;https://github.com/dotnet/corert&quot;&gt;CoreRT&lt;/a&gt; provides a complete native AOT .NET runtime.&lt;/p&gt;

&lt;h3 id=&quot;interpreted-versus-aot&quot;&gt;Interpreted versus AOT&lt;/h3&gt;

&lt;p&gt;Which mode is best? We don’t know yet.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So one obvious thought is that interpreted mode might be for development, and AOT mode might be for production.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2 id=&quot;blazor-a-spa-framework&quot;&gt;Blazor, a SPA framework&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3 id=&quot;components&quot;&gt;Components&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.cshtml&lt;/code&gt; file).&lt;/p&gt;

&lt;p&gt;Razor, which has been around &lt;a href=&quot;https://weblogs.asp.net/scottgu/introducing-razor&quot;&gt;since 2010&lt;/a&gt;, 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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyDialog.cshtml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;my-styles&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;@Title&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  @RenderContent(Body)
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;button&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;onclick=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;@OnOK&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;OK&lt;span class=&quot;nt&quot;&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

@functions {
    public string Title { get; set; }
    public Content Body { get; set; }
    public Action OnOK { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you want to use this component elsewhere, the tooling knows what you’re doing:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2018/02/06/components-tooling.gif&quot; alt=&quot;Animated GIF of Blazor component tooling&quot; style=&quot;width: 500px; max-width:100%&quot; /&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3 id=&quot;infrastructure&quot;&gt;Infrastructure&lt;/h3&gt;

&lt;p&gt;When you create a new project, Blazor will offer the core facilities that most applications need. This includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Layouts&lt;/li&gt;
  &lt;li&gt;Routing&lt;/li&gt;
  &lt;li&gt;Dependency injection&lt;/li&gt;
  &lt;li&gt;Lazy loading (i.e., loading parts of the app on demand as a user navigates)&lt;/li&gt;
  &lt;li&gt;Unit testing harness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Another important design point is that only a few very low-level pieces are baked into the framework. For example, routing and layouts are &lt;em&gt;not&lt;/em&gt; 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.&lt;/p&gt;

&lt;h3 id=&quot;deployment&quot;&gt;Deployment&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dist/&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3 id=&quot;code-sharing-and-netstandard&quot;&gt;Code sharing and netstandard&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;.NET standard&lt;/em&gt; 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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstandard2.0&lt;/code&gt; and below, and you have an assembly that targets &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstandard2.0&lt;/code&gt; or above, then you can use that assembly on that runtime.&lt;/p&gt;

&lt;p&gt;Mono on WebAssembly will support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstandard2.0&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;However, not all .NET APIs make sense in the browser. For example, you can’t listen on arbitrary TCP sockets in a browser, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Net.Sockets.TcpListener&lt;/code&gt; can’t do anything useful. Likewise you pretty much certainly shouldn’t be using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;System.Data.SqlClient&lt;/code&gt; 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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlatformNotSupported&lt;/code&gt; 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 &lt;a href=&quot;https://en.wikipedia.org/wiki/World_Wide_Web&quot;&gt;the world’s most widely deployed app platform&lt;/a&gt; then this is an inevitable stepping stone.&lt;/p&gt;

&lt;h3 id=&quot;javascripttypescript-interop&quot;&gt;JavaScript/TypeScript interop&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;p&gt;To work with third-party JavaScript libraries, we’re exploring the option of using &lt;a href=&quot;https://github.com/DefinitelyTyped/DefinitelyTyped&quot;&gt;TypeScript type definitions&lt;/a&gt; to present them to your C# code with full intellisense. This would make the top 1000-or-so JS libraries trivial to consume.&lt;/p&gt;

&lt;p&gt;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.:&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// This is JavaScript&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;Blazor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;registerFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;doPrompt&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;… and then wrap it for calls from .NET:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// This is C#&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;DoPrompt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RegisteredFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Invoke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;doPrompt&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;registerFunction&lt;/code&gt; approach has the benefit of working nicely with JavaScript build tools such as Webpack.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3 id=&quot;optimisation&quot;&gt;Optimisation&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;all&lt;/em&gt; .NET-based web apps.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Mono runtime stripping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.wasm&lt;/code&gt; file while keeping a basic app working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Publish-time IL stripping&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/dotnet/announcements/issues/30&quot;&gt;.NET IL linker&lt;/a&gt; (originally based on &lt;a href=&quot;https://github.com/mono/linker&quot;&gt;the Mono linker&lt;/a&gt;) does static analysis to figure out which parts of .NET assemblies can ever get called by your app, then it strips out everything else.&lt;/p&gt;

&lt;p&gt;This is equivalent to &lt;a href=&quot;https://webpack.js.org/guides/tree-shaking/&quot;&gt;tree shaking in JavaScript&lt;/a&gt;, 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Compression&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2 id=&quot;whats-the-point-of-it-all&quot;&gt;What’s the point of it all?&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So our initiative here is to put .NET up near the front, rather than trailing years behind.&lt;/p&gt;

&lt;h2 id=&quot;current-status&quot;&gt;Current status&lt;/h2&gt;

&lt;p&gt;Feeling keen to give this a go? Whoa there - we’re still &lt;em&gt;very&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;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!&lt;/p&gt;

&lt;p&gt;If you’re super keen, please have a look &lt;a href=&quot;https://github.com/aspnet/blazor&quot;&gt;the repo&lt;/a&gt;, try building it, run the tests, and &lt;a href=&quot;https://github.com/aspnet/blazor/issues&quot;&gt;talk to us&lt;/a&gt;. Maybe even find a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;// TODO&lt;/code&gt; comment and submit a PR, or tell us about your great feature ideas.&lt;/p&gt;
</description>
        <pubDate>Tue, 06 Feb 2018 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2018/02/06/blazor-intro/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2018/02/06/blazor-intro/</guid>
      </item>
    
      <item>
        <title>Running Blazor on Mono in the browser</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://github.com/stevesanderson/blazor&quot;&gt;Blazor&lt;/a&gt; is an experimental single-page application (SPA) framework that runs on .NET in the browser.&lt;/p&gt;

&lt;p&gt;Unlike prior efforts at getting C# to run client-side, Blazor isn’t transpiling or doing any other fragile tricks: instead, it’s got an actual .NET runtime that loads and runs standard .NET assemblies via WebAssembly (or asm.js for older browsers). This means you get &lt;em&gt;full-fidelity .NET&lt;/em&gt; - everything behaves exactly like .NET on the desktop/server.&lt;/p&gt;

&lt;p&gt;If you want a demo of what it’s like to build an app with it, &lt;a href=&quot;https://www.youtube.com/watch?v=MiLAE6HMr10&amp;amp;feature=youtu.be&amp;amp;t=31m45s&quot;&gt;watch 5 minutes of this video from NDC Oslo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’ll say this again later, but to be sure you’re clear: this is an experiment, and it’s not an official Microsoft product.&lt;/p&gt;

&lt;h3 id=&quot;backstory-it-started-with-dotnetanywhere-dna&quot;&gt;Backstory: it started with DotNetAnywhere (DNA)&lt;/h3&gt;

&lt;p&gt;Back in April, I was trying to think of ways to run .NET under WebAssembly, when coincidentally I stumbled across &lt;a href=&quot;https://news.ycombinator.com/item?id=14008416&quot;&gt;an HN comment&lt;/a&gt; about &lt;em&gt;DotNetAnywhere&lt;/em&gt; (DNA), a .NET runtime I’d never heard of before. A quick look at &lt;a href=&quot;https://github.com/chrisdunelm/DotNetAnywhere&quot;&gt;its GitHub repo&lt;/a&gt; showed that it’s incredibly compact - the whole implementation is just a handful of plain C files. Its extreme simplicity meant that, even though it &lt;em&gt;well&lt;/em&gt; predates WebAssembly and hadn’t even been maintained for over 5 years, it only took a few hours and a few minor tweaks to get it compiled as a WebAssembly binary. To my great surprise, it loaded and executed simple .NET assemblies on the first attempt without any complaints.&lt;/p&gt;

&lt;h3 id=&quot;blazor-an-app-framework&quot;&gt;Blazor, an app framework&lt;/h3&gt;

&lt;p&gt;Blazor takes this basis and lets you build an app with it. It uses Razor files, which combine C# and HTML markup, to define components that present your data and update when anything changes (a bit like React or Angular components). It also inherits Razor Pages concepts such as layouts and so on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is purely experimental.&lt;/strong&gt; You &lt;em&gt;can’t&lt;/em&gt; build a production application with this, for so many reasons. It exists as a way of finding out what a .NET-based SPA framework might look like, and whether people would have any interest in it.&lt;/p&gt;

&lt;p&gt;Nonetheless, some folks from the ASP.NET team and some community members have become interested and contributed a bunch of great enhancements, such as &lt;a href=&quot;https://github.com/ncave&quot;&gt;ncave&lt;/a&gt; doing some very impressive low-level work to improve DNA, and &lt;a href=&quot;https://github.com/davidfowl&quot;&gt;David Fowler&lt;/a&gt; and &lt;a href=&quot;https://github.com/halter73&quot;&gt;Stephan Halter&lt;/a&gt; from the ASP.NET team getting prototype debugging support working as part of a team hackathon event.&lt;/p&gt;

&lt;h3 id=&quot;hitting-limitations-in-dna&quot;&gt;Hitting limitations in DNA&lt;/h3&gt;

&lt;p&gt;It’s extremely impressive that &lt;a href=&quot;https://github.com/chrisdunelm&quot;&gt;Chris Dunelm&lt;/a&gt; single-handedly created an incredibly compact .NET runtime, so compact that the whole thing (including assembly execution, garbage collection, threading) compiles down to 60KB of WebAssembly. Yes, seriously, 60KB - that’s not a typo - and it does actually work. For more about DNA, see &lt;a href=&quot;http://mattwarren.org/2017/10/19/DotNetAnywhere-an-Alternative-.NET-Runtime/&quot;&gt;Matt Warren’s post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But it’s probably not the future. Chris stopped working on DNA about 6 years ago, so it’s a long way behind a modern .NET runtime. It lacks some critical things such as proper reflection support and working with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstandard&lt;/code&gt; assemblies, and there are some known bugs such as crashing with certain types of generic method invocations. And when it crashes, it gives you basically no clue about why.&lt;/p&gt;

&lt;p&gt;During the ASP.NET team hackathon, the thing that most commonly frustrated and slowed people down was not being able to rely on having the complete standard .NET base class library, and not really knowing what runtime features were supported. So .NET in the browser can only be viable if there’s a true, robust, complete production-grade runtime that can run on WebAssembly/asm.js. But where would a miracle like that come from?&lt;/p&gt;

&lt;h3 id=&quot;mono-on-webassembly&quot;&gt;Mono on WebAssembly&lt;/h3&gt;

&lt;p&gt;Not content with just running Mono on all desktop/server platforms, plus iOS/watchOS, Android, smart TVs and the like, &lt;a href=&quot;https://github.com/migueldeicaza&quot;&gt;Miguel de Icaza&lt;/a&gt; from the Mono team has announced a plan to &lt;a href=&quot;http://www.mono-project.com/news/2017/08/09/hello-webassembly/&quot;&gt;bring it to WebAssembly&lt;/a&gt;. And now, &lt;a href=&quot;https://github.com/kumpera&quot;&gt;Rodrigo Kumpera&lt;/a&gt; is just &lt;a href=&quot;https://github.com/mono/mono/pull/5924&quot;&gt;one pull request&lt;/a&gt; away from getting WASM-capable Mono interpreter support merged into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mono:master&lt;/code&gt;. This is exactly what’s needed: a proper, performant, robust, and feature-complete modern .NET runtime on WebAssembly - under active development by a whole team, no less :)&lt;/p&gt;

&lt;h3 id=&quot;blazor-on-mono&quot;&gt;Blazor on Mono&lt;/h3&gt;

&lt;p&gt;Based on Rodrigo’s excellent work, I’ve migrated Blazor to run on Mono. The result is that it has the same features as before, minus the prototype debugging support, but now it executes faster and it supports a vastly more complete .NET API surface, and virtually every aspect of the runtime works correctly like on desktop/server. It even (usually) provides sensible error messages if you cause unhandled exceptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting started&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you want to try it out, install the prerequisites:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;VS2017.3 or later&lt;/li&gt;
  &lt;li&gt;.NET Core 2.0 SDK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;… and then install the &lt;a href=&quot;https://github.com/SteveSanderson/Blazor/releases&quot;&gt;0.3.0 (or later) build of Blazor.VSExtension.vsix, the VS project template&lt;/a&gt;. Then you can do File-&amp;gt;New Blazor application and follow along building an app &lt;a href=&quot;https://www.youtube.com/watch?v=MiLAE6HMr10&amp;amp;feature=youtu.be&amp;amp;t=31m45s&quot;&gt;like in this demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caveats&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;As mentioned before, this is not intended for production use - it’s purely experimental. So let me know what you think of it!&lt;/li&gt;
  &lt;li&gt;Apologies to non-VS users - although this technology is completely cross-platform, I haven’t yet created a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dotnet new&lt;/code&gt; template for it, so at the moment there is only a VS project template.&lt;/li&gt;
  &lt;li&gt;As a result of this migration to Mono, the default Blazor app size has exploded from the reasonable 300KB it was on DNA (which included the runtime, core libraries, application code, etc.) to an almighty 4MB. This is because we’re not yet performing IL-stripping on the builds (i.e, the .NET equivalent of tree shaking), and the Mono runtime is bundling a great many desktop-specific features that are irrelevant on the web. And it’s not even minified. I expect that when we optimise the builds, this size will come down &lt;em&gt;substantially&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sun, 05 Nov 2017 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2017/11/05/blazor-on-mono/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2017/11/05/blazor-on-mono/</guid>
      </item>
    
      <item>
        <title>ASP.NET Core + Angular 2 template for Visual Studio</title>
        <description>&lt;p&gt;Now that ASP.NET Core, Angular 2, and TypeScript 2 have all shipped final versions, it’s a great time to combine them all into one powerful rich web application platform.&lt;/p&gt;

&lt;p&gt;For many months, I’ve been &lt;a href=&quot;https://github.com/aspnet/JavaScriptServices&quot;&gt;working with some great community contributors on our GitHub repo&lt;/a&gt; to build supporting libraries, packages, and ultimately what we hope is the ideal starting-point template for such applications. As well as just the basics of hosting a TypeScript-coded Angular 2 site on ASP.NET Core, the template includes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Server-side prerendering&lt;/strong&gt;, so your UI can show up very quickly, even before the browser downloads your JavaScript code&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Webpack middleware integration&lt;/strong&gt;, so that during development, you don’t need to keep rebuilding your client-side application, or even have to run a watcher tool in the background&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Hot module replacement&lt;/strong&gt;, so that during development, whenever you edit a TypeScript file, a CSS file, or other client-side resource, your changes are pushed into the browser immediately without reloading the page (so you don’t lose your active debugging session, etc.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fast and lean builds&lt;/strong&gt;. In development, you &lt;em&gt;don’t&lt;/em&gt; have to wait for webpack to re-analyse third-party code each time you change your own code, because we factor third-party code out into a separate bundle. Also, in development, your ultra-fast builds include full source maps to aid debugging, whereas in production you get minimal minified output. During publishing to production, it automatically uses production builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What if you want to use a different framework?&lt;/strong&gt; Do you prefer React, React+Redux, or Knockout? We’ve also made &lt;a href=&quot;http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/&quot;&gt;equivalent Yeoman templates for those&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if you want to use a different IDE or develop on Linux or &lt;del&gt;OS X&lt;/del&gt; macOS?&lt;/strong&gt; If you’re not using Visual Studio on Windows, that’s fine: &lt;a href=&quot;http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/&quot;&gt;use our Yeoman generator&lt;/a&gt; to get equivalent Angular 2, React, React+Redux, or Knockout projects that work with VS Code or any other editor on any operating system. &lt;a href=&quot;https://www.microsoft.com/net/core&quot;&gt;.NET Core&lt;/a&gt; is fully cross-platform, after all.&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;First make sure you have installed these prerequisites. Things will not work without them!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.visualstudio.com/en-us/news/releasenotes/vs2015-update3-vs&quot;&gt;Visual Studio 2015 Update 3&lt;/a&gt;. Note that Update 2 is not enough. You need Update 3, because it fixes some issues with NPM, plus it’s a prerequisite for TypeScript 2.0.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blogs.msdn.microsoft.com/dotnet/2016/09/13/announcing-september-2016-updates-for-net-core-1-0/&quot;&gt;.NET Core 1.0.1&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blogs.msdn.microsoft.com/typescript/2016/09/22/announcing-typescript-2-0/&quot;&gt;TypeScript 2.0 for Visual Studio 2015&lt;/a&gt;. If Visual Studio keeps complaining &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cannot find name 'require'&lt;/code&gt;, it’s because you forgot to install this.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js version 4 or later&lt;/a&gt;. We temporarily don’t support Node 0.x because of &lt;a href=&quot;https://github.com/gajus/to-string-loader/issues/9&quot;&gt;this issue&lt;/a&gt;, but might re-add support for Node 0.x in the future. To check your Node version, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node -v&lt;/code&gt; in a command prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you’ve checked the prerequisites are installed, just download and install the &lt;a href=&quot;https://visualstudiogallery.msdn.microsoft.com/31a3eab5-e62b-4030-9226-b5e4c9e1ffb5&quot;&gt;ASP.NET Core Template Pack extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://visualstudiogallery.msdn.microsoft.com/31a3eab5-e62b-4030-9226-b5e4c9e1ffb5&quot;&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/core-template-pack.png&quot; alt=&quot;ASP.NET Core template pack&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;creating-and-running-a-project&quot;&gt;Creating and running a project&lt;/h2&gt;

&lt;p&gt;When you have the prerequisites listed above, and have installed the &lt;a href=&quot;https://visualstudiogallery.msdn.microsoft.com/31a3eab5-e62b-4030-9226-b5e4c9e1ffb5&quot;&gt;ASP.NET Core Template Pack extension&lt;/a&gt;, you can go to Visual Studio’s &lt;strong&gt;File  New Project&lt;/strong&gt; menu, expand the &lt;strong&gt;Web&lt;/strong&gt; category, and pick &lt;strong&gt;ASP.NET Core Angular 2 Starter Application (.NET Core)&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/file-new.png&quot; alt=&quot;File-new menu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Give your project a name and click &lt;strong&gt;OK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, wait patiently as Visual Studio restores all the Node.js (NPM) dependencies! This can take several minutes if your internet connection isn’t screamingly fast.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/restoring.png&quot; alt=&quot;Restoring dependencies&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At this point, you’re likely to encounter what looks like a problem, but actually isn’t. When Visual Studio finishes restoring the NPM dependencies, it will show “&lt;strong&gt;Dependencies - not installed&lt;/strong&gt;”:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/dependencies-not-installed.png&quot; alt=&quot;Dependencies not installed&quot; /&gt;&lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;VS is wrong&lt;/strong&gt;! The fact that you see the packages listed with their version numbers like that means they &lt;em&gt;are&lt;/em&gt; installed. You can ignore the “not installed” message in this particular case, but if it bothers you enough, you can &lt;a href=&quot;http://www.hanselman.com/blog/VisualStudio2015FixingDependenciesNpmNotInstalledFromFseventsWithNodeOnWindows.aspx&quot;&gt;follow Hanselman’s steps to fix it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can now run your project.&lt;/strong&gt; Press Ctrl+F5 to launch without debugging (like any other VS project), or tap on the “Play” icon in the toolbar (labelled &lt;em&gt;IIS Express&lt;/em&gt;) if you’re not into keyboard shortcuts. It will appear:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/template-homepage.png&quot; alt=&quot;Template homepage&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;server-side-prerendering&quot;&gt;Server-side prerendering&lt;/h2&gt;

&lt;p&gt;Have a click around the starter site. You’ll find a couple of examples of simple components built with Angular 2. It doesn’t seem like much is going on, but there are some cool things happening behind the scenes.&lt;/p&gt;

&lt;p&gt;The first thing is that, &lt;strong&gt;even though this is an Angular 2 app that normally runs in the browser, your ASP.NET Core server can run it on the server too&lt;/strong&gt;, so it just sends plain HTML down to the browser that doesn’t even need JavaScript to be displayed.&lt;/p&gt;

&lt;p&gt;To prove this to yourself, try disabling JavaScript in your browser altogether (for Chrome users, open the &lt;a href=&quot;https://developers.google.com/web/tools/chrome-devtools/&quot;&gt;Developer Tools&lt;/a&gt;, press F1, check the &lt;em&gt;Disable JavaScript&lt;/em&gt; box, then reload the page while leaving the Developer Tools open):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/disable-javascript.png&quot; alt=&quot;Disable JavaScript&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You’ll find that your application appears just the same as before, even though your browser can’t execute any client-side code. You can still navigate around by clicking the sidebar links. But note: navigation is the &lt;em&gt;only&lt;/em&gt; thing that will work, because that’s a basic HTML feature. On the “counter” screen, you’ll find the counter button does nothing if you click it, because that’s wired up to a JavaScript event handler, and you don’t have JavaScript right now.&lt;/p&gt;

&lt;h3 id=&quot;so-whats-the-point-of-server-side-prerendering&quot;&gt;So what’s the point of server-side prerendering?&lt;/h3&gt;

&lt;p&gt;The point isn’t really to support browsers that don’t have JavaScript enabled. That would only work in the extreme case where you application has no functionality besides navigation (and in that case, why are you building it as a SPA?).&lt;/p&gt;

&lt;p&gt;The real benefits are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;It gives a dramatic improvement to perceived performance for your users&lt;/strong&gt;. Even if they are on a slow device and a slow internet connection, they get to see your application’s UI in a fraction of a second and perhaps read whatever information you’re showing them. In the background, your potentially large bundle of JavaScript is downloading, parsing, and executing in the background, and then automatically takes over to make your application fully functional on the client. This is much better than just showing a blank screen while the application loads.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It supports web crawlers that might not execute JavaScript&lt;/strong&gt;. As far as any search engine is concerned, you are just returning plain old HTML, so your site can be crawled and indexed in the normal way.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are limitations with server-side rendering. Notably, your application code can’t just assume it always runs in a browser. If you try to reference the browser’s DOM directly, you’ll get an error like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window is undefined&lt;/code&gt; when it runs server-side. Fortunately that’s rarely a problem, because in a well-architected Angular app (or React, etc.), the framework really doesn’t want you to mess with the DOM directly anyway, so you shouldn’t be assuming browser primitives regardless of server-side rendering.&lt;/p&gt;

&lt;p&gt;If you don’t want to use server-side prerendering for some reason, disable it by removing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;asp-prerender-module&lt;/code&gt; attribute from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;app&amp;gt;&lt;/code&gt; element in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Views/Home/Index.cshtml&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;webpack-integration&quot;&gt;Webpack integration&lt;/h2&gt;

&lt;p&gt;The code in this application is written in TypeScript. That means you need a build step before it can be run. The same would be true if you used SASS (compiles to CSS) or wanted your library code to be bundled and minified.&lt;/p&gt;

&lt;p&gt;The dominant build system for modern JavaScript applications today is &lt;a href=&quot;http://webpack.github.io/&quot;&gt;Webpack&lt;/a&gt;. It’s like Grunt or Gulp, but for 2016. It handles TypeScript compilation, bundling and minification, and about a million other things that people have contributed plugins for. We use it in the template, and it enables a couple of cool features:&lt;/p&gt;

&lt;h3 id=&quot;webpack-dev-middleware&quot;&gt;Webpack dev middleware&lt;/h3&gt;

&lt;p&gt;Normally, whenever you change one of your TypeScript files, you’d have to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack&lt;/code&gt; on the  command line to regenerate the compiled JavaScript files under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wwwroot/dist&lt;/code&gt;. But the &lt;em&gt;webpack dev middleware&lt;/em&gt; feature saves you the trouble of doing that.&lt;/p&gt;

&lt;p&gt;If your application is running in development mode, which it is by default when you launch from Visual Studio, then Webpack is running in the background and intercepting any requests for files under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://yoursite/dist&lt;/code&gt;. It handles any such request by returning the compiled file that would be at that location, accounting for any changes you’ve made to the source files.&lt;/p&gt;

&lt;p&gt;Because the Webpack compiler remains active in memory, it’s able to produce incrementally compiled updates in a tiny fraction of the usual build time (usually on the order of a few tenths of a second), so your development experience isn’t &lt;a href=&quot;https://xkcd.com/303/&quot;&gt;interrupted as it otherwise would be&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;hot-module-replacement-hmr&quot;&gt;Hot module replacement (HMR)&lt;/h3&gt;

&lt;p&gt;You know what an incredible drag it is to have to reload your page each time you change something? Well, I admit it’s not the pinnacle of human suffering, but it is a waste of time if you had a debugging session in progress, or otherwise had state in the browser’s memory that will get lost on reload.&lt;/p&gt;

&lt;p&gt;HMR solves this. It’s enabled by default when you’re running in development mode, and it watches for any changes to your Angular application source files (TypeScript, HTML, CSS, etc.). When something changes, it does a fast incremental compilation, using the in-memory Webpack compiler instance, and pushes the changes to any active browser windows. Your application will update on the fly, without reloading the page.&lt;/p&gt;

&lt;p&gt;To see this working, open your browser’s debug console while it’s in development mode (e.g., launched from Visual Studio). You’ll see this message:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/hmr-message.png&quot; alt=&quot;HMR message in debug console&quot; /&gt;&lt;/p&gt;

&lt;p&gt;See &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[HMR] connected&lt;/code&gt;? That means it’s ready to receive changes. Try editing one of your source files. For example, edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientApp/app/components/home/home.component.html&lt;/code&gt; as in this beautiful animated GIF:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/hmr-video.gif&quot; alt=&quot;HMR message in debug console&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The same works if you edit CSS or even images that have been loaded via Webpack &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt; statements.&lt;/p&gt;

&lt;h3 id=&quot;configuring-webpack&quot;&gt;Configuring webpack&lt;/h3&gt;

&lt;p&gt;You’ll find the Webpack configuration files in the project root, called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack.config.js&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack.config.vendor.js&lt;/code&gt;. Now, Webpack is a powerful and sophisticated tool, enough to write whole books about. So don’t be disappointed if it’s not all obvious at first glance. Do be prepared to take some time to learn Webpack if you really want to customise it.&lt;/p&gt;

&lt;p&gt;The only notable thing about how it’s set up in this project is that we’ve split third-party dependency code (i.e., “vendor” code) into a separate bundle, controlled by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack.config.vendor.js&lt;/code&gt; file. This makes rebuilds much faster, because Webpack doesn’t have to re-analyze large libraries like Angular 2 on every build.&lt;/p&gt;

&lt;p&gt;You can add extra third-party dependencies to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack.config.vendor.js&lt;/code&gt;, as in the example below. Whenever you do, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack --config webpack.config.vendor.js&lt;/code&gt; on the command line to update the vendor bundle. If you need to, install the Webpack command-line tool first by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install -g webpack&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;adding-third-party-libraries&quot;&gt;Adding third-party libraries&lt;/h2&gt;

&lt;p&gt;Most JavaScript libraries these days are distributed on NPM. Such libraries are pretty easy to include in your project (assuming they work with the other technologies you’ve chosen, such as Angular 2).&lt;/p&gt;

&lt;p&gt;For example, let’s say you want to use &lt;a href=&quot;http://fontawesome.io/&quot;&gt;Font Awesome&lt;/a&gt;, the “iconic font and CSS toolkit”. In Visual Studio, open your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; file, and add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;font-awesome&quot;: &quot;^4.6.3&quot;&lt;/code&gt; to the list. Visual Studio will auto-complete the package name and suggest the current version number to you. Now save your edited &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; file, and VS will fetch and install your new NPM dependency. Alternatively, you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install --save font-awesome&lt;/code&gt; on the command line.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/font-awesome-in-package-json.png&quot; alt=&quot;Font Awesome in package.json&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now let’s add it to the vendor bundle. Open your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack.config.vendor.js&lt;/code&gt; file, and inside the array called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vendor&lt;/code&gt; (the one that includes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'@angular/common'&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;'@angular/compiler'&lt;/code&gt;, etc.), add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-awesome/css/font-awesome.css&lt;/code&gt;. The order doesn’t matter, but alphabetical is nice:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/vendor-font-awesome.png&quot; alt=&quot;Font Awesome in vendor config&quot; /&gt;&lt;/p&gt;

&lt;p&gt;How did we know to reference &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-awesome/css/font-awesome.css&lt;/code&gt;? Well, if it was a JavaScript library, you’d just reference the package name (e.g., &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moment&lt;/code&gt; for &lt;a href=&quot;http://momentjs.com/&quot;&gt;Moment.js&lt;/a&gt;). But since what we want from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;font-awesome&lt;/code&gt; is a CSS file (and other things referenced by that CSS file, such as fonts or images), we give the path to the CSS file we want, &lt;a href=&quot;http://fontawesome.io/get-started/#download-css&quot;&gt;as specified by the Font Awesome docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next, it’s important to rebuild your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wwwroot/dist/vendor.js&lt;/code&gt; bundle. We don’t rebuild this all the time automatically, because it takes a little while (like 10 seconds maybe). So in a command prompt, at your project root, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack --config webpack.config.vendor.js&lt;/code&gt;. If you don’t already have the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack&lt;/code&gt; tool installed, you’ll need to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install -g webpack&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/webpack-vendor-output.png&quot; alt=&quot;Webpack output&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now Font Awesome’s CSS file (and its other dependencies, such as fonts or SVG files) is in your vendor bundle, so now you can just use it. For example, in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientApp/app/components/navmenu/navmenu.component.html&lt;/code&gt;, you could use Font Awesome’s “calculator” icon:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/font-awesome-usage.png&quot; alt=&quot;Font Awesome usage example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;… and it will show up on your page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/font-awesome-usage-output.png&quot; alt=&quot;Font Awesome usage output&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Note: If you get a message like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Uncaught ReferenceError: vendor_1e69f8aba84bb345782b is not defined&lt;/code&gt;, it’s because either (1) you forgot to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack --config webpack.config.vendor.js&lt;/code&gt; or (2) you did so, but haven’t yet restarted your application. Because vendor file changes are infrequent and take some processing time, we don’t do rebuild them automatically or integrate them with HMR, so on the rare occasions where you modify your vendor bundle contents, you will need to rebuild it yourself and restart your ASP.NET application.ke&lt;/p&gt;

&lt;h2 id=&quot;publishing-to-azure&quot;&gt;Publishing to Azure&lt;/h2&gt;

&lt;p&gt;Before you can deploy, you need to work around a bug in the template that we’ll fix very soon. We (i.e., me) forgot to include &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack-dev-externals&lt;/code&gt; in the list of NPM dependencies, and you do need it during publishing.&lt;/p&gt;

&lt;p&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt;, and in the list of dependencies, add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;webpack-node-externals&quot;: &quot;^1.4.3&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;now-lets-deploy&quot;&gt;Now let’s deploy&lt;/h3&gt;

&lt;p&gt;You can publish an application created with this template to any ASP.NET Core host using any normal deployment mechanism. For those who specifically want to deploy to Azure, here’s one way to do it.&lt;/p&gt;

&lt;p&gt;First, from the Azure portal, create a new Web App.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important! Specify a Node.js version&lt;/strong&gt; If you don’t, it will use an old version, and deployment will fail. Go to the &lt;em&gt;Application settings&lt;/em&gt; pane for your new Web App, and you’ll see a config entry called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WEBSITE_NODE_DEFAULT_VERSION&lt;/code&gt;. Edit the value, entering a recent Node.js version such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6.7.0&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/azure-node-version.png&quot; alt=&quot;Azure configure node version&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After saving that change, go to the &lt;em&gt;Deployment Credentials&lt;/em&gt; config pane, and enter some new credentials.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/azure-deployment-creds.png&quot; alt=&quot;Azure deployment credentials&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then, under &lt;em&gt;Deployment options&lt;/em&gt;, set up &lt;strong&gt;Local Git repository&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/azure-deployment-options.png&quot; alt=&quot;Azure deployment options&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After this, when you go to &lt;em&gt;Overview&lt;/em&gt;, you’ll be able to copy your new &lt;strong&gt;Git clone url&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/wp-content/uploads/2016/10/azure-deployment-git-url.png&quot; alt=&quot;Azure deployment Git URL&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Back in a command prompt at the root of your Angular 2 app (the directory containing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ClientApp&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Startup.cs&lt;/code&gt;, etc.), initialize a Git repo:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git init
git add .
git commit -m &quot;My first commit&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can now add Azure as a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remote&lt;/code&gt; for your Git repo, using the “Git clone url” you got from the Azure portal earlier:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git remote add azure https://your-user-name@my-angular2-site.scm.azurewebsites.net:443/my-angular2-site.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Obviously, don’t just copy and paste what you see above exactly - replace the URL with the one for your own Web App.&lt;/p&gt;

&lt;p&gt;Now you can push the current version of your app to Azure:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git push --set-upstream azure master
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Enter the credentials you chose a moment ago, then sit back and wait while the deployment proceeds. The first deployment will take a few minutes. When it’s finished, visit your site’s URL and be delighted with your magnificent creation!&lt;/p&gt;

&lt;h2 id=&quot;feedback&quot;&gt;Feedback&lt;/h2&gt;

&lt;p&gt;If you have general comments on the scope and direction of this project, please post below! I’d love to know how useful (or not) this is to you. As I mentioned above, if you’re looking for similar project templates for React, React+Redux, or Knockout, or if you’re not using Windows or Visual Studio, you’re not being left out: you can &lt;a href=&quot;http://blog.stevensanderson.com/2016/05/02/angular2-react-knockout-apps-on-aspnet-core/&quot;&gt;use our Yeoman generator&lt;/a&gt; instead.&lt;/p&gt;

&lt;p&gt;Or, if you encounter any specific issues and think you’ve found a bug, please &lt;a href=&quot;https://github.com/aspnet/JavaScriptServices/issues&quot;&gt;file an issue on Github&lt;/a&gt; (please don’t post it as a comment to this blog post)&lt;/p&gt;
</description>
        <pubDate>Tue, 04 Oct 2016 00:00:00 +0000</pubDate>
        <link>https://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/</link>
        <guid isPermaLink="true">https://blog.stevensanderson.com/2016/10/04/angular2-template-for-visual-studio/</guid>
      </item>
    
  </channel>
</rss>