Friday 8 March 2019

Blazor Configuration The AspNetCore Way


I've seen a bit of conversation recently about how you can use configuration in a Blazor (WASM) application and some of the implementations seem a bit convoluted, especially when you consider that AspNet Core already handles it...

So, I don't think it is advisable, but if you really want to...

Create new Blazor project

dotnet new blazor -o BlazorIsAwesome

Update to Blazor 0.9

Update your package references to the latest version as the templates still reference old versions and we all know bleeding edge is best, right?

*Currently the latest version is 0.9.0-preview3-19154-02*

Add AspNetCore packages

You will need the standard Asp.Net Core packages for configuration

<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.2.0" />

THIS ALONE IS REASON ENOUGH NOT TO DO THIS - THINK OF THE PAYLOAD

Configure your app

Open up Startup.cs and add some Using statements to bring in the namespaces required.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.FileProviders;
using System.ComponentModel;

Now we can pull in the configuration to our ConfigureServices method

public void ConfigureServices(IServiceCollection services)
{
var fileProvider = new EmbeddedFileProvider(this.GetType().Assembly);
var configuration = new ConfigurationBuilder()
.AddJsonFile(provider: fileProvider, 
path: "appsettings.json", 
optional: true, 
reloadOnChange: false) 
.Build();
services.AddSingleton<IConfiguration>(configuration);
}

Let's take a look at what that is doing...

We are creating an EmbeddedFileProvider - this comes from the Microsoft.Extensions.FileProviders package - which will give us access to the appsettings.json for our project.

Then we build our configuration using that file provider  and tell it to find the appsettings.json file which is an embedded resource in our Blazor app. 

We say it is optional and not to reload on change - as there's no way an embedded resource is going to change.

Finally we add the configuration to our services collection, so it can be injected into the app wherever it is needed.

An Embedded Appsettings.json

Add a new appsettings.json file to the project - don't put it in wwwroot!

Add some settings - in this case I'm just adding a title:

{ "Title" : "My Blazor App (appsettings.json)" }

You set the appsettings.json as an embedded resource by selecting it, pressing F4 and changing the property "Build Action" to "Embedded resource" 

For the hard-core coders, you can also do this in csproj

<ItemGroup>
  <Content Remove="appsettings.json" />
</ItemGroup>

<ItemGroup>
  <EmbeddedResource Include="appsettings.json">
    <CopyToOutputDirectory>Never</CopyToOutputDirectory>
  </EmbeddedResource>
</ItemGroup>

Now Let's Use This Thing

To make use of the configuration we have added, we need to inject an IConfiguration object into the code

I'm going to use Index.cshtml for this:

@page "/"
@using Microsoft.Extensions.Configuration
@inject IConfiguration  configuration

<h1>@configuration["Title"]</h1>

I have added a using statement to bring in the Configuration namespace.
I injected the IConfiguration object from the DI container.
Finally, I grab the "Title" from configuration and place it in an <h1> 





Saturday 2 March 2019

CSS In Your Razor Component is Broken


The current preview of Razor Components (0.8.0-preview-19104-04) broke the ability to ship content files from a component library. The ASP.NET team will fix it, but in the meantime, what can you do?

You've heard of CSS in JS? 
Let's try CSS in Components.
It's not what it sounds like.

CSS In Components

Instead of shipping a CSS file, just create a CSS component

Here's my Razor Component's CSS

BlazorScrollBarCSS.cshtml
Notice it is a Razor View, not a CSS file.

Now, in my BlazorScrollBar component, I can reference the classes from the CSS component and ship the CSS as part of the library again.

All the consuming project needs to do is place the CSS component somewhere in it's pages, for example, in App.cshtml.


This seems like a reasonable short term solution for supplying CSS with your components, until the problem is resolved.

Additional Benefit

Because the CSS is now encapsulated as a component, we can allow the consuming project more control over whether the CSS is required.

I have a parameter on my BlazorScrollBar component 


...and an if statement in the Component markup


So, the consumer has the choice, they can manually include the CSS by placing the CSS component somewhere in their app, they can have the ScrollBar component include the CSS in it's own markup, or they can choose not to use my CSS.


Thursday 28 February 2019

Uploading Larger Files In Razor Components

Just a short note on enabling larger transfers in Razor Components.

By default, the SignalR buffer is set at about 18KB in a new Razor Components application.

This limits how much data you can transfer between Browser and Server as you will see errors such as this if you overflow this buffer:

System.InvalidOperationException: Advancing examined to the end would cause pipe to deadlock because FlushAsync is waiting.
The current solution is to add some SignalR options to your Razor Components Configure method in Startup.cs (Server Project)


I am not advocating always changing these values, and I'm not saying set them to zero - but I have certainly been able to upload 13MB images this way. YMMV.

Choose a buffer size that works for you seems to be the current advice.

Sample Project

I have created a sample project on Github https://github.com/SQL-MisterMagoo/RCFileUpload

This project simply demonstrates how to upload multiple image files to the server and displays a gallery of images.


Extra Reading

After working on this for a while, I found this old post https://www.radzen.com/blog/read-image-base64-blazor-signalr/ by Vladimir Enchev, which takes a similar, approach - but slightly different in that their code only handles single file uploads at a time, while my sample shows a method for handling multiple files. But I would recommend reading Vladimir's post - the more information you get, the better your understanding.

Friday 1 February 2019

Handling Version Updates in a Blazor PWA

Last time we saw how to enable some basic offline first / progressive web app behaviours for our Blazor application. Now, let's enable update notifications so the users know when we publish a new version.

Service Worker Updates


It's really simple to notify clients of an update, but there are a couple of quirks that you should be aware of first.

The browser will identify that you have an update by comparing a hash of your Service Worker file each time it is fetched from the server. When a change is identified in the file, the update process begins.

If the user has visited your site before, they will have an existing Service Worker running in the browser, which cannot be overwritten while it is in use, so your new file gets staged, waiting for activation. 

At this point, the new Service Worker can (and should) populate a new cache of files required to load and run your application.

Activation of the new Service Worker will happen when the page is reloaded by the user.

How will the user know there is an update? 
You tell them. 
However you want to notify them, it should be a user choice to reload when they are ready.

When the page reloads, the old Service Worker is retired and the new one is activated (well, ok - it may be - more on this later).

It's at this point that the user will see the new release of your amazing Blazor application.

Our New Service Worker


The only change we want to make to our Service Worker from the previous post is a command that tells the browser to go ahead and update when the user reloads the page.

Earlier, I mentioned that the page reload will retire the old worker and activate the new one - that's not the whole story, but if we modify our install handler we can make that happen.

In our blazorServiceWorker,js file, we need to modify the event listener for install : 

self.addEventListener('install', event => {
  self.skipWaiting();
  event.waitUntil(
    caches.open(staticCacheName).then(function (cache) {
      return cache.addAll(filesToCache);
    })
  );
});

The new line self.skipWaiting(); tells the browser that we want it to activate our new Service Worker as soon as the old one is retired. 

Without this line, the user has to close all browser tabs that are connected to your site before the update can happen - not a nice flow!

Registering For Update Notifications


We need to know when there is an update, so we need to add some code to the Service Worker registration that we can hook into.

This code comes in various flavours (yes, you are reading a British blog), I got this flavour from "zwacky's" post on medium.

It took me a while to understand - and I highly recommend taking that time and making that effort - but understanding the code is vital for when you have to debug why your application won't udpate.

So, in blazorSWRegister.js we replace the original code with this:

window.updateAvailable = new Promise(function (resolve, reject) {
  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/blazorServiceWorker.js')
      .then(function (registration) {
        console.log('Registration successful, scope is:', registration.scope);
        registration.onupdatefound = () => {
          const installingWorker = registration.installing;
          installingWorker.onstatechange = () => {
            switch (installingWorker.state) {
              case 'installed':
                if (navigator.serviceWorker.controller) {
                  resolve(true);
                } else {
                  resolve(false);
                }
                break;
              default:
            }
          };
        };
      })
      .catch(error =>
        console.log('Service worker registration failed, error:', error));
  }
});

The change we are making is to wrap the Service Worker registration in an async method (Promise) which is added to the window object, so that we can create a hook from JavaScript to Blazor.

After registering the Service Worker, it attaches an event handler for updatefound, which allows us to detect when the Service Worker has been installed.

When we call it, we pass a DotNetReference that will be the entry point to our Blazor application and allow us to notify the end user.

JavaScript Interop


This is new for this project - we need to be able to subscribe to the Service Worker registration Promise created above from our Blazor application, and we do this through JSInterop.

We'll put this in a new file called blazorFuncs.js

window.blazorFuncs = {
  registerClient: function (caller) {
    window['updateAvailable']
      .then(isAvailable => {
        if (isAvailable) {
          caller.invokeMethodAsync("onupdateavailable").then(r => console.log(r));
        }
      });
  }
};

Here, we are creating a method we can call from Blazor, with a parameter that will be a Blazor component with a JSInvokable callback function - identified as onupdateavailable.

The registerClient function subscribes to the Promise we just created - and when that Promise resolves, we invoke the JSInvokable instance method onupdateavailable of our Blazor component caller.

Blazor Needs To Know


So, we've got a situation coded up where the browser will detect a new Service Worker, and we have a mechanism for receiving notifications in Blazor, but we haven't actually hooked that up.

I have decided to hook this up in my MainLayout because I want to display something to the user whichever page they are on.

In the C# code for the MainLayout view, we need to add an OnInitAsync override, where we call registerClient, and pass it a DotNetReference to the MainLayout.

protected override async Task OnInitAsync()
{
  await JSRuntime
    .Current
    .InvokeAsync<object>(
      "blazorFuncs.registerClient", 
      new DotNetObjectRef(this)
    );
}

We also need to create the JSInvokable method that will receive notifications when the Service Worker is being updated.

[JSInvokable("onupdateavailable")]
public async Task<string> AppUpdate()
{
  Console.WriteLine("New version available");
  updateReady = true;
  StateHasChanged();
  return await Task.FromResult("Alerted client");
}

For simplicity, I am simply going to display a message in the top bar to inform the user.

This is controlled by a boolean defined on the MainLayout 

bool updateReady;

And some markup in the Razor section of the view

<div class="main">
  <div class="top-row px-4">
    <p class="text-muted mb-0">Version 0.1</p>
    @if (updateReady)
    {
      <p class="text-info mb-0">
      There is an update available. Reload when ready.
      </p>
    }
    <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
  </div>
 
  <div class="content px-4">
    @Body
  </div>
</div>

Recap


In this post, I have shown how we can add update notifications to the Offline First PWA we built previously.

To make the client app update, we need to remember that the browser will only detect an update when the Service Worker file changes. This change detection is based on a hash of the file contents, so any change is enough to trigger the update flow.

It is recommended that you always modify the name of your cache - in this example, that is defined in the constant staticCacheName. This will cause a flush and re-load of the cache and ensure the browser has picked up all your code changes before the user receives the update.

Happy coding!

Wednesday 30 January 2019

Creating an Offline-First Progressive Web App in Blazor


In this post, I will cover the basics of turning your Blazor application into an Offline-First Progressive Web App that can be installed on a user's desktop / home screen.

Offline First

This describes a behaviour where your application will display basic information even when the client computer is not connected to the internet, or has poor connectivity.
So, none of this...

Browser offline page notification

The Service Worker

To achieve offline-first behaviour, we need to cache the minimum set of assets that allow the application to function offline, and we will write a Service Worker to handle this.

Our service worker is simply a javascript file that implements a basic set of methods, the first of which is install, where we create a cache of the apps assets.

For a Blazor application, this is a lengthy list as we need to cache the application and all it's dependencies.

Create a new file under the wwwroot folder in your Blazor application and call it give it a meaningful name - mine is blazorServiceWorker.js
const filesToCache = [
    // Blazor standard requirements
    '/_framework/_bin/Microsoft.AspNetCore.Blazor.Browser.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Blazor.dll',
    '/_framework/_bin/Microsoft.AspNetCore.Blazor.TagHelperWorkaround.dll',
    '/_framework/_bin/Microsoft.Extensions.DependencyInjection.Abstractions.dll',
    '/_framework/_bin/Microsoft.Extensions.DependencyInjection.dll',
    '/_framework/_bin/Microsoft.JSInterop.dll',
    '/_framework/_bin/Mono.WebAssembly.Interop.dll',
    '/_framework/_bin/mscorlib.dll',
    '/_framework/_bin/System.Core.dll',
    '/_framework/_bin/System.dll',
    '/_framework/_bin/System.Net.Http.dll',
    '/_framework/wasm/mono.js',
    '/_framework/wasm/mono.wasm',
    '/_framework/blazor.boot.json',
    '/_framework/blazor.webassembly.js',
 
    // App specific requirements
    '/_framework/_bin/BlazorAppUpdates.dll',
    '/_framework/_bin/BlazorAppUpdates.pdb',
    '/css/bootstrap/bootstrap.min.css',
    '/css/open-iconic/font/css/open-iconic-bootstrap.min.css',
    '/css/site.css',
    '/favicon.ico',
    '/index.html',
 
    // Service Worker
    '/blazorSWRegister.js',
 
    // Application Manifest (PWA)
    '/manifest.json'
];
 
const staticCacheName = 'blazor-cache-v1';
self.addEventListener('install', event => {
    self.skipWaiting();
    event.waitUntil(
        caches.open(staticCacheName)
            .then(cache => {
                return cache.addAll(filesToCache);
            })
    );
});

Here, we are adding an event listener for the install event, which will create a browser cache and add all of the files listed in filesToCache.

filesToCache is simply an array listing all the assets our application requires to function offline.

staticCacheName is where we define the name of the cache - it is good practice to include a version number here, so we can manage cache recycling for each new version of our application.

Now we have code to configure our application assets cache but it is not being called from anywhere, so let's take a small diversion to set that up.

Create another JavaScript file under wwwroot - mine is called blazorSWRegister.js.
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/blazorServiceWorker.js')
        .then(function (registration) {
            console.log('Registration successful, scope is:', registration.scope);
        })
        .catch(function (error) {
            console.log('Service worker registration failed, error:', error);
        });
}

This code is checking that Service Worker is supported in the browser and passing our service worker JS file to the register method. There are a couple of console logs just for the developer.

Add the register JS file to your index.html
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width">
    <title>BlazorAppUpdates</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/site.css" rel="stylesheet" />
 
    <!-- Register our service worker -->
    <script src="blazorSWRegister.js"></script>
</head>

So, now we have code that will run on startup to register the service worker, which will then cache all the files needed to work offline.

But, we don't have anything to serve the cached files when they are needed, so let's add some more code to our service worker that will intercept web requests and serve them from the cache. We can do this in the fetch event handler.
self.addEventListener('fetch', event => {
    const requestUrl = new URL(event.request.url);
 
    // First, handle requests for the root path - server up index.html
    if (requestUrl.origin === location.origin) {
        if (requestUrl.pathname === '/') {
            event.respondWith(caches.match('/index.html'));
            return;
        }
    }
    // Anything else
    event.respondWith(
        // Check the cache
        caches.match(event.request)
            .then(response => {
                // anything found in the cache can be returned from there
                // without passing it on to the network
                if (response) {
                    console.log('Found ', event.request.url, ' in cache');
                    return response;
                }
                // otherwise make a network request
                return fetch(event.request)
                    .then(response => {
                        // if we got a valid response 
                        if (response.ok) {
                            // and the request was for something rfom our own app url
                            // we should add it to the cache
                            if (requestUrl.origin === location.origin) {
 
                                const pathname = requestUrl.pathname;
                                console.log("CACHE: Adding " + pathname);
                                return caches.open(staticCacheName).then(cache => {
                                    // you can only "read" a response once, 
                                    // but you can clone it and use that for the cache
                                    cache.put(event.request.url, response.clone());
                                });
                            }
                        }
                        return response;
                    });
            }).catch(error => {
                // handle this error - for now just log it
                console.log(error);
            })
    );
});

I have included comments in the code, but the logic is just this

  • Request for the root "/" path => get index.html from cache and return that.
  • If the request was for anything else, check the cache and return from the cache if found.
  • If it wasn't found in the cache, let the request out to the network
  • If the network responds ok, and the request was for something from our own URL, add it to the cache and return it
  • Return the network response.
Now you should have an offline first app that will cache itself and fall back to the network for any external requests.

Note: This is an MVP implementation - in the real world you will also need code to clean up the cache and to allow for updates when you publish a new version. Look out for a follow up post about those.


Progressive Web App

This describes a behaviour where, for the purpose of this post, your application will be installable on the user's desktop/home screen and will launch in it's own window / context.

Thankfully, this is also simple - it's just a manifest.json file in wwwroot

{
  "short_name": "Blazor",
  "name": "Blazor PWA",
  "icons": [
    {
      "src": "/images/android-icon-192x192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/android-icon-512x512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/",
  "background_color": "#3367D6",
  "display": "standalone",
  "theme_color": "#3367D6"
}

I suggest reading about manifest.json elsewhere, just know that "display":"standalone" is what makes it open like a native app on the users' machine - after they choose to install it, and you should include at least two icons at 192x192 and 512x512.

You add this as a link in the head of your index.html
<head>
...
    <!-- Manifest for enabling PWA -->
    <link href="manifest.json" rel="manifest" />
...
</head>

Now, when you visit your application URL, (I think the convention is you have to visit at least two times several minutes apart?) the browser should prompt you to install the PWA.

If it doesn't, in Chrome you can open the browser menu (three vertical dots in the top right corner) and it should have an option to "Install your_app_name...".

Summary

You now have the tools to make your Blazor app into an installable offline-first PWA, but there is more to do!

You may have Apis that you call - what behaviour do you want from your application when they are not accessible?

Add code to your application to gracefully handle this - remember you have only cached the minimum required for your application so far.

Perhaps you read a feed of some kind and display posts in your application - you can add code to the service worker to store those posts (in IndexedDB for example) and serve them from there when they are offline.

This is just the beginning - have fun!

Want to try out Blazor? Head to Blazor.net

edit: added the app's pdb to the cache list - seems to be required at this stage at least during development - presumably not when published.
edit: remove extra reference to blazorCache.js - should not have been there - thanks kiyote!