受欢迎的博客标签

ASP.NET Core PWA Series: Turning a ASP.NET Core website into a Progressive Web App (PWA)

Published

Usually, web applications are deployed in web servers like IIS, Apache, Nginx, etc., In this article, we will be deploying our web application as a windows service and setup our front-end as a PWA, so that we can have an App like user experience.

.Net 5.x

https://www.iaspnetcore.com/blog/blogpost/608c3cd2b8d10f02212ad3fa/how-to-create-progressive-web-applications-with-aspnet-core-blazor-webassembly-step-by-step

 

Progressive Web Apps are built using Html, CSS & JavaScript. Since this a web-based application it can run across devices and operating systems.  PWA’s are a great choice especially when our solution should run cross-platform(windows, android & iOS).

Target Customers

An individual customer going to use it as a personal app from their mobile devices (Android / iOS).
Medium to Large scale organizations is going to use this app as a cloud-based SaaS service.
Small Shops going to use this app as an offline desktop application.

Technologies used in this article

PWA framework for Front-End
.NET Core for backend WebAPI.  

 

 

Use WebEssentials.AspNetCore.PWA

Progressive Web Apps (PWA) is a principle covering quite a lot of different web requirements, but what they all have in common are that they better the user's experience. A large part of these principles has to do with availability of content and using new net standards. A full checklist of these principles according to Google can be seen here.

The first step for converting your site to a PWA is to implement a Service Worker and create a Manifest for your site.

The Manifest is a json file stating some details about your site. It makes it possible for a user to add your site to the home screen on their phone or desktop. The Service Worker makes caching easy and defines some rules for if content is not available.

A Service Worker gets registered through some JavaScript and the different rules for handling the cache can take some time to grasp. So, we are lucky to see there is a NuGet Package for simplifying the setup of a Service Worker and handling your Manifest. The package we are going to use is WebEssentials.AspNetCore.PWA, which has been developed by Mads Kristensen.

An important detail before starting is that the Service Worker will only work if your site is SSL-encrypted and redirects to HTTPS. The package makes some services available for you that can be added to the container through the ConfigureServices method in your Startup class

public void ConfigureServices(IServiceCollection services)
{
    // Your other services
    services.AddProgressiveWebApp();
}

 

WebManifest class c#

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;

namespace WebEssentials.AspNetCore.Pwa
{
    //
    
    //     The Web App Manifest
    public class WebManifest
    {
        //
        
        //     The absolute file path to Web App Manifest file.
        [JsonIgnore]
        public string FileName
        {
            get;
            internal set;
        }

        //
        
        //     The raw JSON from the manifest file.
        [JsonIgnore]
        public string RawJson
        {
            get;
            internal set;
        }

        //
        
        //     A name for use in the Web App Install banner.
        [JsonProperty("name")]
        public string Name
        {
            get;
            set;
        }

        //
        
        //     A short_name for use as the text on the users home screen.
        [JsonProperty("short_name")]
        public string ShortName
        {
            get;
            set;
        }

        //
        
        //     Provides a general description of what the web application does.
        [JsonProperty("description")]
        public string Description
        {
            get;
            set;
        }

        //
        
        //     .
        [JsonProperty("iarc_rating_id")]
        public string IarcRatingId
        {
            get;
            set;
        }

        //
        
        //     .
        [JsonProperty("categories")]
        public IEnumerable<string> Categories
        {
            get;
            set;
        }

        //
        
        //     Specifies the primary text direction for the name, short_name, and description
        //     members. Together with the lang member, it can help provide the correct display
        //     of right-to-left languages.
        [JsonProperty("dir")]
        public string Dir
        {
            get;
            set;
        }

        //
        
        //     Specifies the primary language for the values in the name and short_name members.
        //     This value is a string containing a single language tag.
        [JsonProperty("lang")]
        public string Lang
        {
            get;
            set;
        }

        //
        
        //     If you don't provide a start_url, the current page is used, which is unlikely
        //     to be what your users want.
        [JsonProperty("start_url")]
        public string StartUrl
        {
            get;
            set;
        }

        //
        
        //     A list of icons.
        [JsonProperty("icons")]
        public IEnumerable<Icon> Icons
        {
            get;
            set;
        }

        //
        
        //     A hex color value.
        [JsonProperty("background_color")]
        public string BackgroundColor
        {
            get;
            set;
        }

        //
        
        //     A hex color value.
        [JsonProperty("theme_color")]
        public string ThemeColor
        {
            get;
            set;
        }

        //
        
        //     Defines the developer's preferred display mode for the web application.
        [JsonProperty("display")]
        public string Display
        {
            get;
            set;
        }

        [JsonProperty("orientation")]
        public string Orientation
        {
            get;
            set;
        }

        //
        
        //     pecifies a boolean value that hints for the user agent to indicate to the user
        //     that the specified related applications are available, and recommended over the
        //     web application.
        [JsonProperty("prefer_related_applications")]
        public bool PreferRelatedApplications
        {
            get;
            set;
        }

        //
        
        //     Specifies an array of "application objects" representing native applications
        //     that are installable by, or accessible to, the underlying platform.
        [JsonProperty("related_applications")]
        public IEnumerable<RelatedApplication> RelatedApplications
        {
            get;
            set;
        }

        //
        
        //     Defines the navigation scope of this web application's application context.
        [JsonProperty("scope")]
        public string Scope
        {
            get;
            set;
        }

        //
        
        //     Check if the manifest is valid
        public bool IsValid(out string error)
        {
            if (string.IsNullOrEmpty(Name) || string.IsNullOrEmpty(ShortName) || string.IsNullOrEmpty(StartUrl) || Icons == null)
            {
                error = "The fields 'name', 'short_name', 'start_url' and 'icons' must be set  in " + FileName;
                return false;
            }

            if (!Icons.Any((Icon i) => i.Sizes?.Equals("512x512", StringComparison.OrdinalIgnoreCase) ?? false))
            {
                error = "Missing icon in size 512x512 in " + FileName;
                return false;
            }

            if (!Icons.Any((Icon i) => i.Sizes?.Equals("192x192", StringComparison.OrdinalIgnoreCase) ?? false))
            {
                error = "Missing icon in size 192x192 in " + FileName;
                return false;
            }

            error = "";
            return true;
        }
    }
}

The AddProgressiveWebApp method adds both a Service Worker and the references to your manifest, but these can be added independently of each other using the AddServiceWorker and AddWebManifest methods. The PWA settings can be changed in the appsettings.json like so

{
	//Other settings,
	"pwa": {
		"cacheId": "Worker 1.1",
		"strategy": "cacheFirstSafe",
		"routesToPreCache": "/Home/Contact, /Home/About",
		"offlineRoute": "fallBack.html",
		"registerServiceWorker": true,
		"registerWebmanifest": true
	}
}

cacheId defines a unique id for the current version of your site and will be used in your Service Worker to check if the user currently has the newest version of the page.

strategy selects a strategy for your Service Worker among some predefined profiles. cacheFirstSafe is the default strategy which caches every resource and ensures each is of the Doctype text/html on the first try gets accessed via the network to get the newest version and then falls back to the cache if this is not available.

It also caches resources with the v querystring since it expects this to be unique content. cacheFirst is the same as the safe one, but it does not go network first on text/html resources and does not cache resources with the the v querystring, so this should only be used for sites you do not expect to change frequently.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

networkFirst goes network first for all resources and adds the resource to the cache when doing so, which it then falls back to if it can't get the resource. minimal does not cache at all and its primary use would be to prompt for installation of the PWA on phones using the Manifest.

routesToPreCache states which resources should be pre-cachet meaning that it will be added to the cache as soon as the Service Worker has been registered. This is especially useful for caching all the subpages on your site, so that the user can browse these if he later revisits them while being offline.

offlineRoute specifies a route (which will be cached) for if the user tries to access a page which was not cached before the user went offline. This can often be avoided by adding the page to routesToPreCache or uniquely identifying the resources using the v querystring, but it's a better alternative compared to the "There is no connection" page in Chrome.

registerServiceWorker and registerWebmanifest are for disabling individual features in the service, but the standard is default for both.

The settings can alternatively be changed in the Startup class by giving the AddProgressiveWebApp a PwaOptions object. A pro for this would be that the settings could be changed programmatically like the following example where the cacheId is defined using a version variable which could originate from a database or likewise.

public void ConfigureServices(IServiceCollection services)
{
	//Your other services
	services.AddProgressiveWebApp(new PwaOptions
	{
		CacheId = "Worker " + version,
		Strategy = ServiceWorkerStrategy.CacheFirst,
		RoutesToPreCache = "/Home/Contact, /Home/About"
		OfflineRoute = "fallBack.html",
	});
}

Already now you can check if your service has been registered by opening the Chrome DevTool and going to Application > Service Worker.

DevTool Applikation View

manifest.json

Then we just need to add a manifest for your site, which would normally be referenced as a link in the header of your pages, but this has also taken care of via the WebEssentials.AspNetCore.PWA. The manifest defines some details for your site if it's downloaded, which includes custom splash-screens, icon-standards and more. Furthermore, does it also prompts the user to install it as a PWA, via an install-banner, if they visit the site frequently on a phone. This is one of the core properties of a PWA (Progressive Web App) since it gives the immersive App part. The only thing you need is to make a new json called manifest.json and place it in the wwwroot. The file should have the following pattern:

{
	"name": "My Progressive Web App",
	"short_name": "My PWA",
	"orientation": "portrait",
	"display": "standalone",
	"icons": [
		{
			"src": "images/logox192.png",
			"sizes": "192x192",
			"type": "image/png"
		},
		{
			"src": "images/logox512.png",
			"sizes": "512x512",
			"type": "image/png"
		}
	],
	"start_url": "/",
	"color": "#ffffff",
	"background_color": "#20c4f4",
	"theme_color": "#20c4f4"
}

name is the full name of your site. short_name is what will be displayed if a user adds your site to their homepage on a smartphone or desktop. orientation specifies if your website should be viewed in portrait or landscape mode or using the keyword any, if it's up to the user, when it has been installed. display sets the way your site will be presented after download. The default is browser, which just opens your site in the standard browser. Alternatives are standalone, minimal-ui or full screen, which all create a splash-screen for your site while it loads. The splash-screen will show the icon for the page and the name. icons makes icons in different sizes available via a list representation.

A minimum requirement is to have both 192x192 and 512x512, but more can be added. start_url defines the page the site will start on when opened. color sets the text color on the splash-screen. background_color sets the background color on the splash-screen.
theme_color defines the site-wide theme-color of your page, which currently only is a property used in Google Chrome. This property also has to be added to each page individually with a meta-tag for your manifest to be valid, but our PWA package injects this on our pages automatically, so we don't need to bother.

There are multiple other properties which can be added to the manifest, but these are the most basic. More can be found on Google's post The Web App Manifest. The Manifest is now ready and the users will be prompted to add your page to their home screen if it's the correct syntax.

Add to homescreen

These are the first steps to making your site a PWA. Other specifications that must be followed is fast response time (also on slow 3G), mobile friendly design and fast response time. An easy way to test to see if your site is up is by using the Chrome tool Lighthouse, which rates your site's use of PWA, your site's performance and speed, accessibility of your content and best practices for a modern web page. This makes it easy to see how far along you are in the process and gives you help on what to do next to improve.