Implementing Progressive Web Application With ASP.NET MVC

Why you should be using Progressive Web Apps?

Progressive Web Apps (PWAs) are Web Apps which combines the best features of Web and Native Apps. In short, it can be described as “The best of the web, plus the best of native apps”. The biggest advantage is cost-saving in terms of app development and maintenance. Because it is assumed that making a website is easier than making native apps for different platforms like Android, iOS and the web.

With the native apps, the hardest problem is distribution. Mobile apps can take up a lot of storage space on your phone. Progressive Web App stake up far less space, on average, and they could save you a significant amount of storage and bandwidth (from downloading apps).

In this post, we are going take a look at how we can implement Progressive Web App behavior into existing or new ASP.NET MVC 5 app. If you are new to Progressive Web Apps (PWAs) then Google Developer Web Network has something for you to read.

 Progressive Web Apps are user experiences that have the reach of the web, and are:  

 Reliable – Load instantly and never show the down sour, even in uncertain network conditions.     
 Fast – Respond quickly to user interactions with silky smooth animations and no janky scrolling.   
 Engaging – Feel like a natural app on the device, with immersive user experience.

This new level of quality allows Progressive Web Apps to earn a place on the user’s home screen.
Google Developer Web Network

Configuring and creating Progressive Web Apps (PWAs) using ASP.NET Core 2.0 is pretty straightforward with Visual Studio 2017 as you can add this behavior using NuGet Package WebEssentials.AspNetCore.PWA. But if you are working with ASP.NET MVC 5 then it requires some manual addition of JavaScript and JSON files. This post talks about how to add PWA behavior to ASP.NET MVC 5 app and how to extend it with a simple example.

This article considers your site is served over HTTPS (required for service workers) and pages are responsive on tablets & mobile devices because these are the basic items in baseline progressive Web App checklist. So, we will concentrate on the most challenging part of implementing PWA behavior into existing MVC app.

How to implement PWA behavior to ASP.NET MVC 5 app


  • Open your existing ASP.NET MVC 5 solution.
  • Adding icons of different sizes inside the Content folder.

Now, add manifest.jsonfile in the root of the MVC project. You can get more information about manifest.json (or manifest.webmanifest) at MDN Web Docs.

The web app manifest provides information about an application (such as its name, author, icon, and description) in a JSON text file. The manifest informs details for websites installed on the home screen of a device, providing users with quicker access and a richer experience


{
  "name": "ASP.NET MVC Progressive Web App",
  "short_name": "PWA App",
  "description": "Implementing PWA behavior into existing ASP.NET MVC 5 app.",
  "theme_color": "#2196f3",
  "background_color": "#2196f3",
  "display": "standalone", 
  "start_url": "/",
  "icons": [
    {
      "src": "Content/images/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "Content/images/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "Content/images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "Content/images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "Content/images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "Content/images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "Content/images/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "Content/images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
   "related_applications": [
  {
      "platform": "play",
      "url": "https://play.google.com/store/apps/details?id=com.example.app1",
      "id": "com.example.app1"
    },
    {
       "platform": "itunes",
       "url": "https://itunes.apple.com/app/example-app1/id123456789"
    }
  ]  
}

Add above code to your manifest.json file and include the same file in <head> of _Layout.cshtml file like below.

   <link rel="manifest" href="/manifest.json">

Also, add theme-color Meta tag with valid coloring _Layout.cshtml file.

<meta name="theme-color" content="#2196f3" />

  • Now, we need to have an offline page to show on network availability. So, either you can create controller OfflineController and put some informatory text in Index.cshtml
  • or create standalone offline.html and place it in the root directory. Add a new JavaScript file (seviceworker.js) for service worker in the root of the project.
Have a look Google Developer Web Network to know more about service workers.



(function () {
    'use strict';

    // Update 'version' if you need to refresh the cache
    var version = 'v1.0::CacheFirstSafe';
    var offlineUrl = "Offline"; // <-- Offline/Index.cshtml
    var urlsToCache = [ '/', offlineUrl ]; // <-- Add more URLs you would like to cache.

    // Store core files in a cache (including a page to display when offline)
    function updateStaticCache() {
        return caches.open(version)
            .then(function (cache) {
                return cache.addAll(urlsToCache);
            });
    }

    function addToCache(request, response) {
        if (!response.ok && response.type !== 'opaque')
            return;

        var copy = response.clone();
        caches.open(version)
            .then(function (cache) {
                cache.put(request, copy);
            });
    }

    function serveOfflineImage(request) {
        if (request.headers.get('Accept').indexOf('image') !== -1) {
            return new Response('<svg role="img" aria-labelledby="offline-title" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title id="offline-title">Offline</title><g fill="none" fill-rule="evenodd"><path fill="#D8D8D8" d="M0 0h400v300H0z"/><text fill="#9B9B9B" font-family="Helvetica Neue,Arial,Helvetica,sans-serif" font-size="72" font-weight="bold"><tspan x="93" y="172">offline</tspan></text></g></svg>', { headers: { 'Content-Type': 'image/svg+xml' } });
        }
    }

    self.addEventListener('install', function (event) {
        event.waitUntil(updateStaticCache());
    });

    self.addEventListener('activate', function (event) {
        event.waitUntil(
            caches.keys()
                .then(function (keys) {
                    // Remove caches whose name is no longer valid
                    return Promise.all(keys
                        .filter(function (key) {
                            return key.indexOf(version) !== 0;
                        })
                        .map(function (key) {
                            return caches.delete(key);
                        })
                    );
                })
        );
    });

    self.addEventListener('fetch', function (event) {
        var request = event.request;

        // Always fetch non-GET requests from the network
        if (request.method !== 'GET' || request.url.match(/\/browserLink/ig)) {
            event.respondWith(
                fetch(request)
                    .catch(function () {
                        return caches.match(offlineUrl);
                    })
            );
            return;
        }

        // For HTML requests, try the network first, fall back to the cache, finally the offline page
        if (request.headers.get('Accept').indexOf('text/html') !== -1) {
            event.respondWith(
                fetch(request)
                    .then(function (response) {
                        // Stash a copy of this page in the cache
                        addToCache(request, response);
                        return response;
                    })
                    .catch(function () {
                        return caches.match(request)
                            .then(function (response) {
                                return response || caches.match(offlineUrl);
                            });
                    })
            );
            return;
        }

        // cache first for fingerprinted resources
        if (request.url.match(/(\?|&)v=/ig)) {
            event.respondWith(
                caches.match(request)
                    .then(function (response) {
                        return response || fetch(request)
                            .then(function (response) {
                                addToCache(request, response);
                                return response || serveOfflineImage(request);
                            })
                            .catch(function () {
                                return serveOfflineImage(request);
                            });
                    })
            );

            return;
        }

        // network first for non-fingerprinted resources
        event.respondWith(
            fetch(request)
                .then(function (response) {
                    // Stash a copy of this page in the cache
                    addToCache(request, response);
                    return response;
                })
                .catch(function () {
                    return caches.match(request)
                        .then(function (response) {
                            return response || serveOfflineImage(request);
                        })
                        .catch(function () {
                            return serveOfflineImage(request);
                        });
                })
        );
    }); 

})();


Now you can verify by opening the application using Chrome browser open Chrome Developer Tools (Ctrl + Shift + I)under Application Tab, the service worker is activated and running with the application. You can use Lighthouse in Chrome Dev Tools to run an audit for your PWA.



Conclusion

We have implemented PWA behavior to our MVC application, although there are many things that can take a PWA from a baseline to exemplary experience. You can find more material on Google Developer Web Network as Google is promoting PWA vastly through Chrome Dev Summit and Google Chrome supports it fully.

Thank you for reading. Keep visiting our blogs and share this in your network. Please put your thoughts and feedback in the comments section.

1 Comments

  1. Progressive web apps are web apps that use emerging web browser apis and features along with traditional progressive enhancement strategy to bring a native app-like user experience to cross-platform web applications. With progressive web app development, web developers can create reliably fast web pages and offline experiences.

    ReplyDelete