How to send push notifications to a browser in ASP.NET Core
Progressive Web Apps (PWAs) enables a website to make make a lot of interactions that are app-like. Among these are Push Notifications. This is a functionality that enables you to make native notifications for many different devices and to invoke these notifications even when the browser is not active. A lot of websites like the Twitter web app use this functionality with its original intent, but there are of cause also sites who use it maliciously. In this article, we will show how you can subscribe to Push Notifications using ASP.NET Core and how you can send Push Notifications from .NET as well.
Setting up a minimal PWA
Before we can get started we need to set up a minimal Service Worker and Manifest to meet the minimum requirements for a PWA (Progressive Web App) since this we need a PWA for using the Notification API.
We first create the manifest by making a JSON file called
manifest.json which we will place in the
Here we define the name for the app, the name that will be displayed in the app, if installed on a device, icons for the app in a variety of sizes, the front page URL of the site if the website can be installed as a standalone app, and at the end theme colors. We add a reference to the manifest in the
head of our layout file which is used in all our views
<link rel="manifest" href="/manifest.json">.
ServiceWorker.js, also in the
wwwroot folder with the following content.
We reference the service worker by inserting the following script either in a script tag element in your layout file or directly in your main js file if you have such one.
This code simply checks if your browser supports Service Workers and registers your Service Worker if it does so. Now we have a working Manifest and Service Worker.
Subscription and push flow
There are 3 primary actors in the subscription/push flow. The web page (includes Service Worker), the push service, and the server. This flow uses a pair of keys, a private and public from a standard called VAPID (Voluntary Application Server Identification). There are other ways to secure the communication e.g. FCM (Firebase Cloud Messaging), but VAPID doesn’t require you to use any specific platform so we use that. To start we need to make a key pair and save this in our app settings. It’s an open standard that uses elliptic curve cryptography and there are a lot of implementations that generate these pairs like the python library py-vapid, the node module web-push or the online tool tools.reactpwa.com/vapid. We will generate a pair and add it to
private key is never used in other places than on the server and should be kept a secret. The
public key is distributed to the web page and the push service to validate messages. We will send the public key in the next part and the webpage will distribute it to the push service when subscribing. The
public key is used when subscribing so that the push messages from the server can be authenticated because they are signed using the
private key. When the web page subscribes to the push service it gets back an
endpoint which the server will use to send its push message and the two variables
auth will be used to identify the subscription since they are used when constructing the message.
Subscribe to Push Notifications
We now want to make a view where we go through a few steps to subscribe to Push Notifications. We separate this into 3 states. First, we have a part that we will display if the user has not decided if they will allow Notifications. Then we have a part that we will display either if the browser does not support Notifications or if the user has blocked Notifications. Last, we have a form that we will use to submit an identifier for the user, the
endpoint, and the two subscription identification variables
There are quite a lot of functions in this script block. Let’s go through them one at a time.
First, we register our Service worker again. We do this both to check if it can be registered. If it can’t then we display the UI appropriate UI part. We also do it to get the
Registration object for the Service Worker. We then check if the user has given access to make Notifications. If they granted access then we show the form and call the
getSubscription function which also fills out the hidden fields in the form. If they have blocked notifications then we show the no-support UI part. The third scenario is that the user has not decided yet in which case we will show the UI part for requesting access to make notifications. We also subscribe to the click event for the
PromptForAccessBtn in which case we will invoke the
requestNotificationAccess function. The following is how it looks when the prompt is invoked in Chrome on desktop.
requestNotificationAccess function simply requests permission for making notifications and reacts to the result of the prompt. If they granted the permission then we show the form and call the
getSubscription function once again and if they block we show the no-support UI part.
getSubscription function we want to get a subscription object from the Service Worker. We first try to get it by calling
reg.pushManager.getSubscription. This method returns a subscription object if there already exists one. If it did return a subscription then we parse that to the
fillSubscribeFields function. If there was no subscription then we try to make one by calling
reg.pushManager.subscribe. We parse the
public key to this function using the razor
ViewBag. To parse this from the controller we simply add the following to our controller and action for this view. In my small example, I just use the
HomeController since this is a small example.
Note that we inject
IConfiguration into the controller, but that we have not added it in the
startup.cs file. This can be done because
IConfiguration is automatically dependency injected in ASP.NET. If
reg.pushManager.subscribe returns a subscription then we simply use that to call
fillSubscribeFields now. If something goes wrong and we do not get a subscription then we have no other option than to log an error. This error could occur if the
public key is not generated correctly.
fillSubscribeFields function fills out the hidden inputs in the form. The
endpoint is available as a field in the subscription object. The
auth variables are available through the
getKey function on the object but they are returned as Array Buffers. So we use a function we have made called
arrayBufferToBase64 which converts Array Buffers to a base 64 string.
Saving the subscription
We have made a form that posts the subscription information. We need to define the action that receives this post and save it somehow. We create a new action in our controller.
We define that this action handles a post by setting the attribute
[HttpPost]. The action takes the same arguments as we have parsed to it from the form. The goal of this action to save the given parameters so that they can be used later. We use a placeholder for any kind of persistent storage which we call
PersistentStorage. This could be any kind of database or even Azure Blob Storage. In the body, we first check if the submitted client name is null in which case we return
BadRequest. This should probably be handled with an error screen of some sort if this was a real scenario. We also check if the client’s name is already used in which case we also return
BadRequest. Then if things are looking good we make a new
PushSubscription object which takes all the subscription details as arguments. The
PushSubscription class is from the package WebPush-NetCore which handles how to send a notification for us. We then save the
PushSubscription object in our persistent storage. If this were to be saved in a database then there would probably be needed some serialization/deserialization process for this or as an alternative just save each field individually. In the end, we return a new view in which we can make the push notification. We will let anyone have access to this page for demonstration purposes, but this page should probably only be available for administrators in most cases. We will make this action and view after the next section.
Event listeners in Service Worker
Now, we have made it so that the server has the endpoint and the keys it needs to make a push notification. But before we push a notification we need to make the listeners that handle when a notification is pushed. These are defined in our Service Worker. First, we make the listener for a new push message by adding the following to
The event listener receives a package containing some data. The data is in our case just plain text which we retrieve by calling
.text(). It could have been a JSON object in which case we would call
.json() instead. If it’s an empty message then we just default to some standard message. Then comes the primary part of this function, the
options object. This defines what text will be displayed in the notification, the icon that will be shown, how the notification will vibrate (only available on phones), and extra data that some operating systems might use when showing the notification. We can also define different actions in the
options object. We have made two actions. One which indicates that something will happen and another which closes the notification. We have defined an ID, the text for the action, and an icon for the action for each action. The icon is optional We use the
options object as an argument to the
showNotification which makes the actual notification. A title for the notification is also defined in this function and would normally be the same as the name of your web app as this will be shown with your logo at the top of the notification on most platforms. We have defined two actions, but we have not yet defined what happens when they are used. For this, we define another event listener in the Service Worker.
This listener gets a package that contains a reference to the notification and an
action field that contains the ID for the action that was selected. We then close the notification if they press the
Pushing the notification
Now, we have made it to the last part: Actually sending the push notification. First, we make a new view in which we will pick a client that we will push a message. This is the
Notify view that we have referenced earlier.
The following picture shows the result of this picture.
The view uses a list of strings as its model. Each string will represent a client. We parse this list to the view from our action.
We first check if there are no strings in the list and display a fitting message if so. If there are strings in the list, then we are ready to go. We make a form that will post to an action which we also call Notify. In the form, we first make an input for a message that we will send. After that, we make a radio button for each of the clients in the list with corresponding labels. Now we just need to make an action for the post.
In the action, we first check if a client was selected and if the client exists. Then we extract the
private key, and our email from our previously injected
IConfiguration. These are passed to the constructor of a new
VapidDetails which is also from the WebPush-NetCore package. Then a
WebPushClient is created which can send the Push Notification. The
SendNotification method is called in a try-catch block. This is done because there are a couple of different errors that occur e.g. if the user has unsubscribed from notification or if the service worker has been updated without re-subscribing. In the end, the
Notify view is returned again so that a new notification can be pushed. The following video shows the result of this process.
We monitor your websites for crashes and availability. This helps you get an overview of the quality of your applications and to spot trends in your releases.
We notify you
We notify you when errors starts happening using Slack, Microsoft Teams, mail or other forms of communication to help you react to errors before your users do.
We help you fix bugs
We help you fix bugs quickly by combining error diagnostic information with innovative quick fixes and answers from Stack Overflow and social media.
This content was originally published here.