Fermer

mars 25, 2019

Notifications Push dans les applications angulaires ASP.NET Core


Les notifications push sont apparues pour la première fois dans les applications mobiles natives et, maintenant, avec les applications Web progressives, elles ont été intégrées aux navigateurs modernes. Apprenez à les utiliser dans une application angulaire basée sur ASP.NET Core pour étendre la portée et enrichir l'UX de votre application.

Vous savez certainement ce que sont les notifications push – vous les avez rencontrées plusieurs fois dans la nature. Oui, ils peuvent être utilisés à mauvais escient, mais s'ils sont utilisés correctement, ils peuvent élargir votre audience et enrichir leur expérience. Dans cet article, nous allons créer une application Web ASP.NET Core basée sur Angular avec des notifications push. Nous allons procéder étape par étape pour que vous puissiez coder, mais si vous préférez tout parcourir et télécharger une application prête à l'emploi, c'est aussi une option. En cours de route, je vais essayer de fournir des informations générales sur les notifications push afin que vous puissiez mieux les comprendre.

Premières choses à commencer: création d'une application angulaire basée sur ASP.NET Core

Création d'un nouveau site Web ASP.NET Core. L’application, qui utilise le projet CLI angulaire comme interface utilisateur, est aussi simple que d’ouvrir Visual Studio, en cliquant sur Fichier → Nouveau → Projet en sélectionnant Application Web ASP.NET Core puis en choisissant Angular à partir des modèles disponibles. Pour ceux d'entre vous qui préfèrent la ligne de commande, la commande équivalente est dotnet new angular . L'application créée est prête à être exécutée, mais le premier démarrage peut prendre quelques instants, car tous les packages npm doivent être téléchargés.

À première vue, l'application ressemble à tout autre projet ASP.NET Core. La première différence notable est la présence de AddSpaStaticFiles UseSpaStaticFiles et UseSpa dans la classe Startup . Angular.PushNotifications
{
  classe publique Startup
  {
    public Void ConfigureServices (services IServiceCollection)
    {
      // En production, les fichiers angulaires seront servis à partir de ce répertoire
      services.AddSpaStaticFiles (configuration =>
      {
        configuration.RootPath = "ClientApp / dist";
      });
    }

    public void Configure (application IApplicationBuilder, env. IHostingEnvironment)
    {
      app.UseSpaStaticFiles ();
      app.UseSpa (spa =>
      {
        spa.Options.SourcePath = "ClientApp";
        if (env.IsDevelopment ())
        {
          spa.UseAngularCliServer (npmScript: "start");
        }
      });
    }
  }
}

Vous avez probablement deviné que le dossier ClientApp est important. C'est là que réside le projet Angular CLI. Les méthodes UseSpa * sont là pour informer le noyau ASP.NET du fait qu'il doit prendre en charge la gestion de ce projet CLI angulaire. Lorsque vous exécutez l'application en mode de développement, ASP.NET Core exécute une instance du serveur de CLI angulaire en arrière-plan. Lorsque vous publiez l'application, la version de production ne contiendra que du code compilé et détaillé (vous n'avez pas besoin de Node.js sur votre serveur). Avec tout ce qui reste à faire, il ne reste que l’implémentation.

Modification de l’application

L’application nouvellement créée est prête à être utilisée et affiche même certaines données, mais pour en faire un bon matériel de démonstration les choses devraient être modifiées.

Commençons par le backend. Il contient un seul contrôleur.

 [Route("api/[controller] ")]
Classe publique SampleDataController: Controller
{
  [HttpGet("[action] ")]
  Public IEnumerable  WeatherForecasts ()
  {
    // ...
  }

  Classe publique WeatherForecast
  {
    // ...
  }
} 

Tout d'abord, le nom est terrible. Si nous voulons fournir des prévisions météorologiques, le contrôleur doit être nommé en conséquence. En outre, la partie ASP.NET Core de l'application est conçue pour servir d'API pour la partie interface utilisateur. Il serait donc agréable d'en faire un contrôleur d'API correct. Cela signifie ajouter ApiControllerAttribute et renommer la méthode.

 [Route("api/[controller] ")]
[ApiController]
Classe publique WeatherForecastsController: Controller
{
  [HttpGet]
  public IEnumerable  Get ()
  {
    // ...
  }

  Classe publique WeatherForecast
  {
    // ...
  }
} 

Nous pouvons maintenant passer à la partie interface utilisateur. Le composant chargé d'extraire et d'afficher les prévisions météorologiques se trouve dans le répertoire ClientApp / src / app / .

 @Component ({
  sélecteur: 'app-fetch-data',
  templateUrl: './fetch-data.component.html'
})

classe d'exportation FetchDataComponent {
  prévisions publiques: WeatherForecast [];

  constructeur (http: HttpClient, @Inject ('BASE_URL') baseUrl: string) {
    http.get  (baseUrl + 'api / SampleData / WeatherForecasts'). subscribe (résultat => {
      this.forecasts = résultat;
    }, erreur => console.error (erreur));
  }
}

interface WeatherForecast {
  // ...
} 

La ​​première étape devrait être de changer de fetch-data dans le répertoire des composants et les noms de fichier en les prévisions météorologiques . Ensuite, le composant lui-même peut être renommé, son sélecteur modifié et l'URL de GET modifiée pour refléter les modifications côté serveur. À la fin, cela devrait ressembler à ce qui suit:

 @Component ({
  sélecteur: 'app-météo-prévision',
  templateUrl: './weatherforecast.component.html'
})

classe d'exportation WeatherForecastComponent {
  prévisions publiques: WeatherForecast [];

  constructeur (http: HttpClient, @Inject ('BASE_URL') baseUrl: string) {
    http.get  (baseUrl + 'api / WeatherForecasts'). subscribe (résultat => {
      this.forecasts = résultat;
    }, erreur => console.error (erreur));
  }
}

interface WeatherForecast {
  // ...
} 

Deux autres composants sont présents: home et counter . Ceux-ci peuvent être simplement supprimés. Il ne reste plus qu'à ajuster les modifications du menu de navigation et du module de niveau supérieur aux modifications suivantes:

 import {AppComponent} de './app.component';
importer {NavMenuComponent} de './nav-menu/nav-menu.component';
importer {WeatherForecastComponent} de './weather-forecast/weatherforecast.component';

@NgModule ({
  déclarations: [
    AppComponent,
    NavMenuComponent,
    WeatherForecastComponent
  ],
  importations: [
    BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
    HttpClientModule,
    FormsModule,
    RouterModule.forRoot([
      { path: '', component: WeatherForecastComponent, pathMatch: 'full' }
    ])
  ],
  fournisseurs: [],
  bootstrap: [AppComponent]
})

classe d'exportation AppModule {} 

Nous disposons ainsi d'une application Web simple qui affiche les prévisions météorologiques. Le diagramme ci-dessous illustre ses principaux composants.

 Application angulaire alimentée par le noyau ASP.NET

Conditions requises pour les notifications push

Avant de pouvoir étendre l'application avec les notifications push, nous devons comprendre leur fonctionnement. . Il s'agit d'un de ces cas où une image vaut mille mots.

 Notifications Web Push

Le diagramme doit indiquer que les notifications push utilisent un modèle d'abonnement. S'il y a un abonnement, il doit y avoir un abonné. Cela ne peut pas être la page Web elle-même – cela ne serait pas pratique. Même si nous ne prenions en compte que les applications d'une seule page, où l'établissement d'une connexion avec le service push peut être effectué une fois au chargement, il serait problématique de transmettre les détails de l'abonnement dans les deux sens. Les navigateurs ont quelque chose de plus approprié pour ce rôle, quelque chose qui est toujours en arrière-plan: les travailleurs du service. C'est un travailleur de service qui agit en tant qu'abonné, maintient une connexion avec le service Push et peut recevoir des notifications. Ainsi, les détails de l'abonnement ne doivent être partagés qu'une seule fois avec l'application dorsale.

La présence d'un service push permet à l'application dorsale de ne pas tenir compte du fait qu'un client est actuellement connecté ou non. Le serveur demande simplement la livraison à tout moment; le service push se chargera d'attendre un client.

La manière dont les notifications push nous obligent à ajouter quelques composants à notre application.

 Application angulaire optimisée avec notifications push pour ASP.NET

Ajout d'un agent de service angulaire

Le composant le plus important manquant du côté client (le cœur des notifications push) est l'agent de service. Angular est livré avec son propre Angular Service Worker qui doit être ajouté à l'application. La meilleure façon de le faire est d'utiliser la CLI angulaire . Lancez une invite de commande et accédez au dossier ClientApp

Avant de pouvoir ajouter le Angular Service Worker vous devez mettre Angular à jour de 6.1 à 7.X.

 ] npm installer -g rxjs-tslint
rxjs-5-to-6-migrate -p src / tsconfig.app.json
ng update @ angular / cli @ angular / core 

Vous pouvez maintenant ajouter le Angular Service Worker au projet.

 ng add @ angular / pwa --project Demo.AspNetCore.Angular.PushNotifications 

Cela va ajouter quelques dépendances, modifier des fichiers de configuration et nous laisser avec un agent de service prêt à recevoir des notifications. La difficulté réside dans le fait que l'application n'utilisera pas ce service worker lorsque nous essayons de l'exécuter à partir de Visual Studio. En effet, en mode Development ASP.NET Core utilise ng serve pour exécuter ClientApp tandis qu'Angular déploie son service worker uniquement avec ng build. --prod . La modification requise ici n'est pas difficile. Tout d’abord, l’appel UseAngularCliServer de Le démarrage doit être supprimé, puis le fichier de projet doit être modifié pour pouvoir exécuter ng build --prod non plus après ComputeFilesToPublish mais aussi avant Build .


  
  
  
    
    
    
  
  
  

Préparation du backend ASP.NET Core pour les notifications push

Le côté client est prêt à recevoir des notifications push (il n'y a toujours aucun moyen de s'abonner, mais nous y arriverons), il est donc temps de travailler du côté du serveur. Le côté serveur nécessite deux fonctionnalités: la gestion des abonnements et l'envoi de notifications. Avant de pouvoir commencer tout travail, nous devons générer une paire de clés Identification volontaire de serveur d'application (VAPID) . Ces clés sont utilisées pour l'identification de l'application serveur et la signature de notification. Le moyen le plus simple de les générer est probablement à l'aide de la bibliothèque web-push de Node.js .

 npm install web-push -g
web-push generate-vapid-keys --json 

Il est préférable de placer les valeurs générées dans appsettings.json .

 "PushNotifications": {
  "PublicKey": "BMBuVtMBpcgwRtUNttNj2yXP3PGCSrf_fT94pCb1Bdl1Jdn1H8_CSK0GXqa8hOAkLq1EYnTH__zaXhy5jLoJ4S2A",
  "PrivateKey": "6GJW3jlOQonru2IsakRLpqj2d6qURK2C9GCZSlYwKq8"
} 

Nous pouvons maintenant passer à la mise en oeuvre. Nous ne souhaitons pas implémenter tous les détails du Web Push Protocol – il est préférable d'utiliser un client de notifications Push existant. Ici, je vais utiliser Lib.Net.Http.WebPush . Commençons par la gestion des abonnements.

Un abonnement est représenté par l'objet PushSubscription . Ce qu'il faut, c'est un contrôleur capable de gérer une demande de stockage et de suppression d'abonnements.

 [Route("api/[controller] ")]
[ApiController]
Classe publique PushSubscriptionsController: ControllerBase
{
  privé en lecture seule IPushSubscriptionsService _pushSubscriptionsService;

  public PushSubscriptionsController (IPushSubscriptionsService pushSubscriptionsService)
  {
    _pushSubscriptionsService = pushSubscriptionsService;
  }

  [HttpPost]
  public void Post ([FromBody] abonnement à PushSubscription)
  {
    _pushSubscriptionsService.Insert (inscription);
  }

  [HttpDelete("{endpoint}")]
  public void Delete (chaîne de caractères)
  {
    _pushSubscriptionsService.Delete (noeud final);
  }
} 

L'implémentation de IPushSubscriptionsService n'est pas si importante, elle doit fournir des capacités de stockage et c'est tout. Pour une simple démonstration, LiteDB est une excellente approche.

 Classe publique PushSubscriptionsService: IPushSubscriptionsService, IDisposable. 


{
  lecture seule privée LiteDatabase _db;
  lecture seule privée LiteCollection  _collection;

  public PushSubscriptionsService ()
  {
    _db = new LiteDatabase ("PushSubscriptionsStore.db");
    _collection = _db.GetCollection  ("subscriptions");
  }

  public void Insert (abonnement PushSubscription)
  {
    _collection.Insert (abonnement);
  }

  public void Delete (chaîne de caractères)
  {
    _collection.Delete (subscription => subscription.Endpoint == endpoint);
  }

  public void Dispose ()
  {
    _db.Dispose ();
  }
} 

Le côté client disposera donc maintenant d'un moyen d'informer le serveur des modifications apportées à un abonnement. Toutefois, avant de pouvoir créer un abonnement, l'application cliente doit pouvoir extraire la clé publique VAPID du serveur. L'application serveur peut extraire les clés du fichier de paramètres de la même manière que n'importe quelle autre option.

 public class PushNotificationsOptions
{
  chaîne publique PublicKey {get; ensemble; }
  chaîne publique PrivateKey {get; ensemble; }
}

classe publique Startup
{
  démarrage public (configuration IConfiguration)
  {
    Configuration = configuration;
  }

  public IConfiguration Configuration {get; }
  public Void ConfigureServices (services IServiceCollection)
  {
    services.Configure  (Configuration.GetSection ("PushNotifications"));
  }
} 

Cela permettra d'exposer un contrôleur supplémentaire, qui gérera la demande GET pour la clé publique.

 [Route("api/[controller] ")]
[ApiController]
Classe publique PublicKeyController: ControllerBase
{
  privé en lecture seule PushNotificationsOptions _options;

  public PublicKeyController (options IOptions )
  {
    _options = options.Value;
  }

  public ContentResult Get ()
  {
    return Content (_options.PublicKey, "text / plain");
  }
} 

Le backend offre maintenant la possibilité de gérer les abonnements. Ce qui manque, c'est l'envoi de notifications. Dans ce cas, la meilleure approche est un service hébergé (dans des scénarios réels, on peut aller plus loin et déplacer la responsabilité d'envoi en dehors de l'application Web). Dans cette démonstration, le service hébergé enverra chaque minute une nouvelle valeur de température (aléatoire) aux clients abonnés.

 Classe publique WeatherNotificationsProducer: BackgroundService.
{
  private const int NOTIFICATION_FREQUENCY = 60000;
  lecture seule privée Random _random = new Random ();
  privé en lecture seule IPushSubscriptionsService _pushSubscriptionsService;
  lecture seule privée PushServiceClient _pushClient;

  WeatherNotificationsProducer public (options IOptions IPushSubscriptionsService pushSubscriptionsService, PushServiceClient pushClient)
  {
    _pushSubscriptionsService = pushSubscriptionsService;
    _pushClient = pushClient;
    _pushClient.DefaultAuthentication = new VapidAuthentication (options.Value.PublicKey, options.Value.PrivateKey)
    {
      Subject = "https://angular-aspnetmvc-pushnotifications.demo.io"
    };
  }

  protégé remplacer async tâche ExecuteAsync (CancellationToken stoppingToken)
  {
    while (! stoppingToken.IsCancellationRequested)
    {
      wait Task.Delay (NOTIFICATION_FREQUENCY, stoppingToken);
      SendNotifications (_random.Next (-20, 55), stoppingToken);
    }
  }
} 

Notez l'initialisation PushServiceClient dans le code ci-dessus. L'instance elle-même provient d'une injection de dépendance (je suggérerais l'utilisation de HttpClientFactory pour l'enregistrement), et le service définit l'authentification en fonction des options. Une nouvelle chose est la propriété Subject . Il devrait contenir une URL permettant d'identifier notre application.

Le Web Push Protocol (et PushServiceClient ) ne fait aucune hypothèse concernant le format de la notification. Un exemple PushMessage prend simplement une chaîne comme contenu. Mais le Angular Service Worker attend un format très spécifique. Il doit être l'objet ci-dessous codé en tant que JSON.

 Classe publique AngularPushNotification
{
  Classe publique NotificationAction
  {
    chaîne publique Action {get; }
    chaîne publique Title {get; }

    public NotificationAction (action de chaîne, titre de chaîne)
    {
      Action = action;
      Titre = titre;
    }
  }

  chaîne publique Title {get; ensemble; }
  chaîne publique Body {get; ensemble; }
  chaîne publique Icon {get; ensemble; }
  public IList  Vibrate {get; ensemble; } = nouvelle liste  ();
  IDictionary public  Data {get; ensemble; }
  public IList  Actions {get; ensemble; } = nouvelle liste  ();
} 

Pour faciliter l'utilisation de cet objet, nous pouvons ajouter l'encodage et l'encapsulation JSON dans PushMessage par une méthode pratique.

 public class AngularPushNotification
{
  chaîne de const privé WRAPPER_START = "{" notification  ":";
  chaîne de const privé WRAPPER_END = "}";
  lecture statique privée JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
  {
    ContractResolver = new CamelCasePropertyNamesContractResolver ()
  };

  public PushMessage ToPushMessage (chaîne topic = null, int? timeToLive = null, urgence PushMessageUrgency = PushMessageUrgency.Normal)
  {
    retourne le nouveau PushMessage (WRAPPER_START + JsonConvert.SerializeObject (this, _jsonSerializerSettings) + WRAPPER_END)
    {
      Sujet = sujet,
      TimeToLive = timeToLive,
      Urgence = urgence
    };
  }
} 

À l'aide de cette méthode, l'envoi de notifications ne représente plus que quelques lignes.

 Classe publique WeatherNotificationsProducer: BackgroundService.
{
  Renvoi privé (SendNotifications) (int temperatureC, CancellationToken stoppingToken)
  {
    PushMessage notification = new AngularPushNotification
    {
      Titre = "Nouvelles prévisions météorologiques",
      Corps = $ "Température (C): {températureC} | Temp. (F): {32 + (int) (températureC / 0,5556)}",
      Icon = "assets / icons / icon-96x96.png"
    } .ToPushMessage ();

    foreach (abonnement PushSubscription dans _pushSubscriptionsService.GetAll ())
    {
      // feu et oublie
      _pushClient.RequestPushMessageDeliveryAsync (abonnement, notification, stoppingToken);
    }
  }
} 

S'abonner aux notifications push d'Angular

Il manque un dernier élément du puzzle: un moyen pour les utilisateurs de s'abonner et de se désabonner des notifications. Pour cela, nous pouvons créer un composant. Il peut avoir un balisage très simple. Au final, nous n’avons besoin de rien d’autre qu’un bouton.

 

Nous lions l'événement click du bouton et son étiquette. Le libellé et l'opération dépendront de la présence ou non d'un abonnement actif. Ajoutons un squelette de composant pour aller avec le balisage.

 @Component ({
  sélecteur: 'app-push-abonné',
  templateUrl: './pushsubscriber.component.html',
  styleUrls: ['./pushsubscriber.component.css']
})

classe d'exportation PushSubscriberComponent {
  public operationName: string;
  constructeur () {};
  opération() { };
} 

Comment déterminer s'il existe un abonnement actif ou non? Angular fournit la classe SwPush pour gérer les notifications push pour un Angular Service Worker . Cette classe a une propriété subscription fournissant un observable émettant l’abonnement actuellement actif ou null .

 @Component ({
  // ...
})

classe d'exportation PushSubscriberComponent {
  private _subscription: PushSubscription;
  public operationName: string;

  constructeur (swPush privé: SwPush) {
    swPush.subscription.subscribe ((subscription) => {
      this._subscription = subscription;
      this.operationName = (this._subscription === null)? 'S'inscrire Se désinscrire';
    });
  };

  opération() {
    (this._subscription === null)? this.subscribe (): this.unsubscribe (this._subscription.endpoint);
  };

  private subscribe () {};
  désinscription privée (point de terminaison) {}
} 

Nous savons donc quelle étiquette afficher et quelle opération effectuer. Temps pour l'opération elle-même. L'abonnement s'effectue en trois étapes:

  • Récupérer la clé publique VAPID du serveur
  • Demander un abonnement auprès du prestataire de services
  • Distribuer un abonnement au serveur

Demander un abonnement auprès du prestataire de services Pour ce faire, appelez SwPush.requestSubscription et utilisez les autres étapes HttpClient .

 @Component ({
  // ...
})

classe d'exportation PushSubscriberComponent {
  constructeur(
    swPush privé: SwPush,
    httpClient privé: HttpClient,
    @Inject ('BASE_URL') private baseUrl: string) {
      // ...
    };

  // ...

  private subscribe () {
    // Récupérer la clé publique VAPID du serveur
    this.httpClient.get (this.baseUrl + 'api / PublicKey', {responseType: 'text'}). subscribe (publicKey => {
      // Demander un abonnement auprès du technicien
      this.swPush.requestSubscription ({
        serverPublicKey: publicKey
      })
      // Distribuer un abonnement au serveur
      .then (subscription => this.httpClient.post (this.baseUrl + 'api / PushSubscriptions', abonnement, this.httpOptions) .subscribe (
        () => {},
        erreur => console.error (erreur)
      ))
      .catch (error => console.error (error));
    },
    error => console.error (error));
  };
} 

La ​​désinscription est plus simple. Il faut d'abord appeler le SwPush.unsubscribe puis l'abonnement doit être supprimé du serveur.

 @Component ({
  // ...
})

classe d'exportation PushSubscriberComponent {
  constructeur (...) {
    // ...
  };

  // ...

  désinscription privée (point final) {
    this.swPush.unsubscribe ()
      .then (() => this.httpClient.delete (this.baseUrl + 'api / PushSubscriptions /' + encodeURIComponent (point de terminaison))). subscribe (() => {},
        erreur => console.error (erreur)
      ))
      .catch (error => console.error (error));
  }
} 

Le composant prêt à l'emploi doit être enregistré dans le module d'application, puis peut être placé à l'emplacement que vous jugerez bon pour les utilisateurs.

Vous pouvez maintenant exécuter l'application, puis cliquez sur . Abonnez-vous attendez environ une minute ... et voilà! Une notification intéressante concernant les nouvelles prévisions météorologiques apparaîtra dans votre centre de notification.

Vous pouvez trouver l'application complète ici .

Il y a plus

Cela n'a certainement pas exploré à fond la sujet de notifications push. Cette application ne s’intéresse pas à certains aspects, mais pour qu’elle soit prête pour la production, il doit en gérer quelques autres. Par exemple:

  • 410 et 404 réponses de Push Service. Ils informent que l'abonnement n'est plus valide (expiré, désabonné, etc.) et doit être supprimé de la mémoire.
  • 429 réponse de Push Service. Il indique que la limite de débit a été atteinte et que l'application doit attendre avant de tenter de demander une autre livraison (généralement, elle contient l'en-tête Retry-After ).

Un sujet distinct est en train de redimensionner la remise des notifications push. Les notifications push utilisent des algorithmes de signature et de cryptage puissants. C’est une bonne chose, car le service Push est en fait un homme du milieu, mais il est coûteux en ressources. Dans ce contexte, il est important de garder à l'esprit que demander une livraison ne nécessite pas de contexte demande / réponse. Comme je l'ai déjà indiqué, cela peut être fait en dehors de l'application Web (microservices et fonctions sont une option ici.)

En général, j'espère que les connaissances fournies ici vous permettront de démarrer en douceur le travail avec les notifications push. dans votre application Web ASP.NET Core avec moteur angulaire.

En savoir plus sur ASP.NET Core et Angular

Vous recherchez davantage de contenu de qualité sur le développement avec ASP.NET Core et Angular? Consultez ce guide étape par étape sur Création d'une application Angular 7 avec ASP.NET Core et pour connaître les dernières mises à jour concernant tout ce qui est Angular, rendez-vous sur notre page All Things Angular . post.


Les commentaires sont désactivés en mode aperçu.




Source link