You have developed your Asp.Net Core MVC, it’s working fine and then you want to take it into the next level: to make it installable and allow it to be distributed in the platform store. But a web app is a web app and, as so, it should run in the browser. That would be true some years ago, but not anymore.
There are several ways to transform your web app into an installable app, without rewriting it. One easy way to do it is to transform it into a Progressive Web App (PWA). A PWA is an app that allows to be installed in the hosting OS, is multiplatform, and doesn’t need the browser to be ran. That seems too good to be true, but there is more: it can also use the resources of the hosting OS, as a native app and can be sent and distributed in the platform store.
That seems very nice, no ? And what are the requirements to make a PWA ?
- It should be launchable and installable
- It should be fast, even on slow networks
- It should serve data using HTTPS, not HTTP
- It should offer offline experience
- It should run in all browsers
- It should have responsive design
The full checklist for a PWA can be found here. There is a resource to audit your app, it’s called Lighthouse and can be accessed by opening the Dev Tools (F12):
If you click on Generate report, it will scan your website to see if you have everything to run your app as a PWA:
Ok. Now we know how to check if our website is ready for being a PWA, but what does it take to transform it into a PWA? Basically, two things: a Service Worker and a manifest file.
The Service worker is a script that runs separately from your web page and offers extra functionality, like push notifications and background sync. You can read more about it here.
The app manifest is a json file that will add metadata information for your app. in a way that it can be used to install the app. Sound simple, no? Well, it’s not that difficult, we’ll do it right now.
The first step is to create a service worker, we’ll use the tutorial described in https://developers.google.com/web/tools/workbox/guides/get-started. Let’s create a file named serviceWorker.js and add it to the wwwroot folder:
console.log('Hello from serviceWorker.js');
Then, we’ll register it in the end of the _Layout.cshtml file:
<script>
// Check that service workers are supported
if ('serviceWorker' in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener('load', () => {
navigator.serviceWorker.register('/serviceWorker.js');
});
}
</script>
Once you do that, you can open the dev tools and, in the console, you will see the message:
You can also check in the Application tab, under Service Workers, that it’s up and running:
This works, but it’s not very useful. You can create your own service worker by using Workbox, a tool developed by Google, to develop something that matches your needs. For now, we’ll use workbox cli to generate the service worker (for that, you will have to have npm installed in your machine). Install the workbox cli with:
npm install workbox-cli --global
Then run the wizard, to generate a configuration file:
workbox wizard
Once the wizard has ran, you can generate the service worker with:
workbox generateSW workbox-config.js
That will generate a sw.js file in wwwroot. You have to change the register procedure in _Layout.cshtml to reflect this change:
<script>
// Check that service workers are supported
if ('serviceWorker' in navigator) {
// Use the window load event to keep the page load performant
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js');
});
}
</script>
Once you do that, the next step is to add a manifest file to your app. The easiest way to do it is to use a manifest generator, like this one:
You will need an icon for the app. I went to https://freeicons.io and downloaded an image for my icon and added it to the manifest generator. Then I downloaded a zip with the manifest file and the icons for the app. The manifest file is something like this:
{
"theme_color": "#3558f6",
"background_color": "#2a10b1",
"display": "standalone",
"scope": "/",
"start_url": "/",
"name": "MyMVCApp",
"short_name": "mymvcapp",
"description": "Sample MVC PWA app",
"icons": [
{
"src": "/images/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/images/icon-256x256.png",
"sizes": "256x256",
"type": "image/png"
},
{
"src": "/images/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/images/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
This manifest file must be put on the wwwroot folder of the app with the name of manifest.json and the images, on wwwroot/images folder.
Your PWA is ready. You can run it and see if it’s installable, by checking if this icon is present in Edge (there should be a similar one in the other browsers):
Clicking on it, you will have an install prompt:
If you choose to install it, you will have an app that will run in a window, will have an icon (which you can pin in the taskbar in Windows) and an icon in the start menu:
This app is multi-platform, meaning that you can install it on Linux, Mac or any phone platform. If you press F12 to open the Dev Tools, they will open and then, in the Application tab, you can see the manifest data:
We can check the app in Lighthouse for the PWA compatibility:
From it, you can see three errors
- Redirects HTTP traffic to HTTPS Error!
- Does not provide a valid
apple-touch-icon
- Manifest doesn’t have a maskable icon
If you see the details, you can see they are easy to fix:
The second one needs just adding a non transparent icon to the images folder and add this line in the head section of _Layout.cshtml:
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png">
This will provide a valid apple-touch-icon
The third is just provide a maskable icon for Android devices. This is done adding a purpose in the icon on the manifest:
"icons": [
{
"src": "/images/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose" : "maskable"
},
The first error is already fixed: on the Startup.cs, we already have this:
app.UseHttpsRedirection();
This will allow redirection from http to https, but we are using a port, and we cannot use the same port for http and https (every protocol must have its own port). To test it, we should have it stored in a web site and use the default ports (80 for http and 443 for https). That way we wouldn’t need to use the ports in the address and the test could be done with no problems. If we have IIS installed in our machine, then the problem is solved.
If we don’t have it, we still have an alternative: launch the app in the correct ports. One way to do it is to use the dotnet cli and launch Kestrel with the correct ports. This is done with this command line:
dotnet run --urls "http://localhost:80;https://localhost:443"
It will launch the app with the default ports (that won’t run if you already have some web server listening in those ports), so we can launch our app with http://localhost or https://localhost.
Once you make these changes and run Lighthouse again, you will get:
Now the app passes the PWA test. But this is not everything that a PWA can do. It can interact with the OS, like a native app, and we will see how to do it.
The first thing that can be done is to add a shortcut to the taskbar. This is done in the manifest. by adding a Shortcuts clause. We will add a shortcut for the privacy policy:
"shortcuts": [
{
"name": "Privacy policy",
"short_name": "Privacy",
"description": "View the privacy policy",
"url": "/Home/Privacy",
"icons": [
{
"src": "/images/icon-lock.png",
"sizes": "128x128"
}
]
}
]
You will need to add an icon in the images folder, named icon-lock.png. Once you add these and reinstall the app, when you right click in the taskbar icon, you will see a new menu option:
If you click in this new menu option, it will open a new window with the privacy policy.
One other thing that you can do with PWAs is to access the file system. In the Views\Home folder, create a new FileSystem.cshtml file:
@{
ViewData["Title"] = "File System";
}
<div class="container">
<pre id="textBox" class="pre-scrollable" contenteditable="true">Click on the button to load a file</pre>
<button class="btn btn-secondary" onclick="loadFile()">Load file</button>
<button class="btn btn-secondary" onclick="saveFile()">Save file</button>
</div>
<script>
loadFile = async () => {
const [handle] = await window.showOpenFilePicker();
const file = await handle.getFile();
const text = await file.text();
$("#textBox").text(text);
}
saveFile = async () => {
const options = {
types: [
{
description: 'Text Files',
accept: {
'text/plain': ['.txt'],
},
},
],
};
const handle = await window.showSaveFilePicker(options);
const writable = await handle.createWritable();
await writable.write($("#textBox").text());
await writable.close();
}
</script>
This file will show a text area with two buttons, for load and save a file. The loadFile function will open a OpenFilePicker, get a file handle, open the file and get its text and load the text area. The saveFile function will open a SaveFilePicker, create a writable file and save the textarea text to it. The next step is to add the link to this page in the main menu. In _Layout.cshtml, add this between the Home and the Privacy links:
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home"
asp-action="FileSystem">File System</a>
</li>
When you run the project, you will have a new option that will take you to the file system page:
Now, you have the option to open and save the files to your file system.
Another thing that can be done is to share data with other apps. In the File System page, add a new button:
<button class="btn btn-secondary" onclick="shareData()">Share</button>
The code for the button is:
shareData = async () => {
const data = {
title: 'Sharing data with PWA',
text: $("#textBox").text(),
url: 'https://localhost:5001',
}
try {
await navigator.share(data)
} catch (err) {
alert('Error: ' + err);
}
}
This code will load the text in the text area and will share it with the registered apps. When you run this app and click the Share button, you will see something like this:
If you choose the Mail application, it will open the mail app with the text data:
One other feature that can be enabled is to run the app as a tabbed app. For that, the only thing to do is to enable that, opening the url edge://flags and enabling Desktop PWA tab strips:
When you run the installed app, it will open as a tabbed window:
These are some features available for the PWAs, and more are being added every day. You can check some experimental features in Edge here
All the source code for this project is at https://github.com/bsonnino/MvcPwa