https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-an-sms-dashboard-with-blazor-webassembly/Blog_Blazor-WebAssembly_1200x600.png

Construire un tableau de bord SMS avec Blazor WebAssembly

Publié le November 5, 2020

Temps de lecture : 9 minutes

Si vous avez suivi mon fil Twittervous avez peut-être remarqué que j'ai construit quelques applications de démonstration avec SMS en parallèle, avec Blazor et MVC pur.

Il y a trois fonctionnalités principales à prendre en compte lorsque l'on parle de SMS.

  1. Envoi de messages SMS

  2. Réception de messages SMS

  3. Réception des accusés de réception à partir de vos SMS envoyés

Maintenant qu'ils sont tous sortis d'affaire, il semble que ce soit le bon moment pour s'arrêter et construire quelque chose de plus compliqué. C'est ce que nous allons faire dans ce tutoriel.

Objectifs

Nous allons utiliser Blazor WebAssemblyMVC, le Entity Framework Coreet SignalR pour construire une Single-Page-App(SPA).

Cette application sera capable d'envoyer des SMS bien sûr. Elle pourra également recevoir des SMS ; chaque message reçu sera stocké à l'aide de l'Entity Framework et envoyé directement à nos Applications clientes à l'aide de SignalR. Il fera de même pour tous les accusés de réception qu'il recevra. Ce projet peut sembler un peu ambitieux, mais je vous promets qu'il ne l'est pas. Alors sans plus attendre, commençons notre aventure.

Aller directement au code

Si vous souhaitez voir le code de cette démo, vous pouvez le consulter sur GitHub.

Conditions préalables

  • Nous aurons besoin de Visual Studio 2019 pour utiliser Blazor WebAssembly

  • Nous allons utiliser Ngrok pour tester

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Run Ngrok

Je vais tout mettre en place sur localhost:5000L'exécution de ngrok nous permettra d'accéder publiquement à localhost:5000.

ngrok http --host-header=localhost:5000 5000

Prenez note de l'URL sur laquelle ngrok tourne. Dans mon cas, il tourne sur http://fb09abd3c106.ngrok.io. Cette URL sera l'URL de base pour mes webhooks à l'avenir.

Créez votre solution

Créons notre solution :

  1. Ouvrir Visual Studio

  2. Cliquez sur Créer une nouvelle application

  3. Sélectionner l'application Blazor

  4. Nommez votre application VonageSmsDashboard

  5. Cliquez sur Créer

  6. Sélectionner l'application Blazor WebAssembly

  7. Cochez la case ASP.NET Core hosted en bas à droite et dans le coin.

  8. Cliquez sur Créer

Create a new Blazor ApplicationCreate a new Blazor Application

Après avoir créé votre projet, vous aurez une solution avec trois projets :

  1. VonageSmsDashboard.Client

  2. VonageSmsDashboard.Server

  3. VonageSmsDashboard.Shared

Installer les dépendances

Vous devrez ajouter les paquets NuGet suivants à vos projets respectifs.

VonageSmsDashboard.Server

Vonage
Microsoft.AspNetCore.SignalR.Core
Microsoft.EntityFrameworkCore.Sqlite
Microsoft.EntityFrameworkCore.Design

VonageSmsDashboard.Client

Microsoft.AspNetCore.SignalR.Client

VonageSmsDashboard.Shared

System.ComponentModel.Annotations

Construire notre modèle

Nous allons utiliser EntityFramework Core avec SQLite pour héberger notre base de données. Vous pourriez simplement utiliser les structures de données du Vonage SDK comme modèle, mais comme nous utilisons WebAssembly, cela impliquerait d'envoyer l'ensemble du Nexmo SDK vers le navigateur du client, ce qui quadruplerait la taille du wasm. Pour éviter cela, nous allons ajouter des Plain Old CLR Objects (POCOs) à notre projet. VonageSmsDashboard.Shared à notre projet. Ajoutez les classes suivantes à ce projet :

public class MessageBase
 {
     [Key]
     public string MessageId { get; set; }
     public string To { get; set; }
     public string MessageTimestamp { get; set; }
     public string Msisdn { get; set; }
 }
 public class DeliveryReceiptModel : MessageBase
 {
     public string Status { get; set; }
 }
 public class InboundSmsModel : MessageBase
 {
     public string Text { get; set; }
 }
 public class OutboundSms
    {
        [Key]
        public string MessageId { get; set; }
        public string To { get; set; }
        public string From { get; set; }
        public string Status { get; set; }
        public string MessagePrice { get; set; }
    }

Créer une migration et une base de données

Passez à VonageSmsDashboard.Server et ajoutez le fichier SmsContext.cs au projet. Ajoutez un DbSet pour nos messages entrants et nos Dlrs. Puis surchargez la méthode OnConfiguring pour qu'elle pointe vers une source de données Sqlite.

public DbSet<InboundSmsModel> InboundSms { get; set; }

public DbSet<DeliveryReceiptModel> Dlrs { get; set; }

public DbSet<OutboundSms> OutboundSms { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder options)
   => options.UseSqlite("Data Source=VonageSms.db");

Construire votre projet rapidement, ou dotnet ef risque de supprimer les paquets qui dépendent de vous lorsque vous effectuerez la migration. Maintenant, dans votre console, naviguez jusqu'à VonageSmsDashboard.Server et exécutez ce qui suit :

dotnet tool install --global dotnet-ef
dotnet ef migrations add InitialCreate
dotnet ef database update

Cela produira Migrations\SmsContextModelSnapshot.cs et Migrations\A_TIMESTAMP_InitialCreate.cs Après cela, votre modèle est créé, nous allons maintenant ajouter notre SignalR Hub.

Ajouter un hub SMS

Dans notre VonageSmsDashboard.Server ajouter un dossier appelé Hubspuis ajouter un fichier SmsHub.cs dans ce dossier. Faites en sorte que le SmsHub étende Hub.

public class SmsHub : Hub{}

Configuration de l'intergiciel

Nous allons avoir besoin de mettre en place quelques éléments de middleware pour nous aider à nous installer avec Entity/SignalR.

En Startup.cs trouvez la méthode ConfigureServices et ajoutez-y les deux lignes suivantes :

services.AddSignalR();
services.AddDbContext<SmsDashboardContext>();

Ensuite, dans la méthode Configure ajouter un endpoints.MapHub("/smshub") au délégué UseEndpoints qui se présentera désormais comme suit :

app.UseEndpoints(endpoints =>
{
    endpoints.MapRazorPages();
    endpoints.MapControllers();
    endpoints.MapFallbackToFile("index.html");
    endpoints.MapHub<Hubs.SmsHub>("/smshub");
});

Contrôleur de construction

Une fois notre modèle construit et tous nos intergiciels mis en place, nous allons construire notre contrôleur. En VonageSmsDashboard.Server faites un clic droit sur le dossier Controllers -> ajouter -> Contrôleur -> API Controller - Empty -> Ajouter -> SmsController.cs

Injecter des dépendances

Injectons nos dépendances dans le contrôleur SMS. Nous allons avoir besoin d'un IConfiguration pour extraire notre API_KEY/API_SECRET. Nous aurons besoin d'un IHubContext pour gérer la connexion avec nos clients. Et nous allons avoir besoin d'un SmsContext qui sera notre accès à notre base de données. Injectez-les tous dans le constructeur du contrôleur comme suit :

private readonly IConfiguration _config;
private readonly IHubContext<Hubs.SmsHub> _hubContext;
private readonly SmsContext _dbContext;

public SmsController(IConfiguration config, IHubContext<Hubs.SmsHub> context, SmsContext dbContext)
{
   _hubContext = context;
   _config = config;
   _dbContext = dbContext;
}

Ajouter l'action SendSms

Nous allons maintenant ajouter une action pour envoyer un SMS ; cette action prendra un objet OutboundSms, tirera notre ApiKey/secret de la configuration, enverra un SMS, puis ajoutera l'Id du message, le prix et l'horodatage à notre objet de demande, et le renverra au demandeur.

[HttpPost]
[Route("[controller]/sendsms")]
public async Task<ActionResult<OutboundSms>> SendSms([FromBody] OutboundSms sms)
{
   var apiKey = _config["API_KEY"];
   var apiSecret = _config["API_SECRET"];
   var credentials = Credentials.FromApiKeyAndSecret(apiKey, apiSecret);
   var request = new SendSmsRequest { To = sms.To, From = sms.From, Text = sms.Text };
   var client = new SmsClient(credentials);
   var response = client.SendAnSms(request);
   sms.MessagePrice = response.Messages[0].MessagePrice;
   sms.Status = response.Messages[0].Status;
   sms.MessageId = response.Messages[0].MessageId;
   _dbContext.OutboundSms.Add(sms);
   await _dbContext.SaveChangesAsync();
   return sms;
}

Ajouter des méthodes Get pour le modèle

Ensuite, nous ajouterons des méthodes Get pour chacun de nos types de modèles qui les liront depuis la base de données et les renverront au demandeur.

[HttpGet]
[Route("[controller]/getInboundSms")]
public ActionResult<List<InboundSmsModel>> GetInboundSms()
{
   return _dbContext.InboundSms.ToList();

}

[HttpGet]
[Route("[controller]/getDlr")]
public ActionResult<List<DeliveryReceiptModel>> GetDlr()
{
   return _dbContext.Dlrs.ToList();
}

[HttpGet]
[Route("[controller]/getOutboundSms")]
public ActionResult<List<OutboundSms>> GetOutboundSms()
{
   return _dbContext.OutboundSms.ToList();
}

Ajouter une route pour gérer les SMS entrants et le DLR

Nous avons maintenant besoin d'une route pour gérer les messages SMS et DLR entrants dans notre application. Ces requêtes vont extraire le corps du flux et le désérialiser dans un objet SMS et DLR entrant. Ensuite, les champs critiques seront mappés dans les objets de notre modèle. Ensuite, elle le transmettra aux clients SmsHub. Enfin, il enregistre le nouveau SMS entrant/Dlr dans notre base de données.

[HttpPost]
[Route("webhooks/inbound-sms")]
public async Task<IActionResult> ReceiveSms()
{
   using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
   {
       var json = await reader.ReadToEndAsync();
       var inboundSms = JsonConvert.DeserializeObject<InboundSms>(json);
       var inboundSmsModel = new InboundSmsModel { Msisdn = inboundSms.Msisdn, To = inboundSms.To, MessageId = inboundSms.MessageId, Text = inboundSms.Text, MessageTimestamp = inboundSms.MessageTimestamp };
       await _hubContext.Clients.All.SendAsync("ReceiveMessage", inboundSms);
       _dbContext.InboundSms.Add(inboundSmsModel);
       await _dbContext.SaveChangesAsync();
   }
   return NoContent();
}

[HttpPost]
[Route("webhooks/dlr")]
public async Task<IActionResult> ReceiveDlr()
{
   using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
   {
       var json = await reader.ReadToEndAsync();
       var dlr = JsonConvert.DeserializeObject<DeliveryReceipt>(json);
       var dlrModel = new DeliveryReceiptModel { Msisdn = dlr.Msisdn, To = dlr.To, MessageId = dlr.MessageId, Status = dlr.StringStatus, MessageTimestamp = dlr.MessageTimestamp };
       await _hubContext.Clients.All.SendAsync("ReceiveDlr", dlrModel);
       _dbContext.Dlrs.Add(dlrModel);
       await _dbContext.SaveChangesAsync();
   }
   return NoContent();
}

Construire le Frontend

La dernière chose à faire est de construire le frontend. D'ordinaire, il s'agirait d'assembler HTML/js/CSS jusqu'à ce que tout soit exactement comme nous le souhaitons. La beauté de Blazor wasm est que nous n'avons besoin de penser qu'à un seul type d'unité - nos composants razor. Créons un dossier Components dans le répertoire VonageSmsDashboard.Client dans le répertoire

Désactiver la mise en cache

Tout d'abord, nous allons devoir charger dynamiquement des éléments de notre contrôleur, et donc désactiver la mise en cache du fichier HttpClient. Open Program.cs et remplacer l'appel builder.Services.AddTransient par :

builder.Services.AddTransient(sp =>
{
    var client = new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) };
    client.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue { NoCache = true };
    return client;
});

Créer un composant DeliveredMessage

Dans le dossier VonageSmsDashboard.Client\Components ajoutez un nouveau composant razor "DeliveredMessages". C'est là que nous allons afficher les DLR au fur et à mesure qu'ils arrivent dans notre application. En tête de ce composant, ajoutez des instructions d'utilisation pour les composants Microsoft.AspNetCore.SignalR.Client et VonageSmsDashboard.Shared pour les espaces de noms Injectez ensuite un NavigationManager et un HttpClient. De plus, comme nous allons utiliser un HubConnection pour gérer le push de SignalR, nous allons devoir implémenter IDisposable pour le nettoyer après coup.

@using Microsoft.AspNetCore.SignalR.Client
@using VonageSmsDashboard.Shared;
@inject NavigationManager NavigationManager
@inject HttpClient Http
@implements IDisposable

Ajouter un en-tête et un tableau

Maintenant que nous avons nos dépendances, allons-y et ajoutons la partie visuelle de notre composant. Il s'agira simplement d'un tableau rempli à partir de la liste des messages que nous recevrons du serveur.

<div class="x-display-table">
<h2>Delivered Messages</h2>

<table class="table" id="dlrList">
    <thead>
        <tr>
            <th>To</th>
            <th>From</th>
            <th>Message Id</th>
            <th>TimeStamp</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody>
        @for (var i = _messages.Count - 1; i >= 0; i--)
        {
            var message = _messages[i];
            <tr>
                <td>@message.Msisdn</td>
                <td>@message.To</td> @*note the to = the number you sent from*@
                <td>@message.MessageId</td>
                <td>@message.MessageTimestamp</td>
                <td>@message.String</td>
            </tr>
        }
    </tbody>

</table>
</div>

Ajouter la logique d'initialisation et la connexion SignalR

Nous devons maintenant ajouter quelques champs _hubConnection et _messages pour contenir notre connexion au hub SignalR et les messages DLR, respectivement. Nous devons ajouter une surcharge à OnInitializedAsync pour initialiser le champ HubConnection avec un délégué qui mettra à jour la liste des messages et effectuera une première mise à jour de la liste des messages avec les DLR actuellement disponibles. Nous devrons également ajouter une méthode Dispose pour se débarrasser du HubConnection lorsque le composant sera finalisé. Placez tout cela dans le bloc de code, qui devrait ressembler à ceci :

@code {
    private HubConnection _hubConnection;
    private List<DeliveryReceipt> _messages = new List<DeliveryReceipt>();

    protected override async Task OnInitializedAsync()
    {
        _hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/SmsHub"))
            .Build();
        _hubConnection.On<DeliveryReceipt>("ReceiveDlr", (dlr) =>
        {
            _messages.Add(dlr);
            StateHasChanged();
        });

        await _hubConnection.StartAsync();
        var response = await Http.GetAsync("/sms/getDlr");
        var json = await response.Content.ReadAsStringAsync();
        _messages.AddRange(Newtonsoft.Json.JsonConvert.DeserializeObject<List<DeliveryReceipt>>(json));
    }


    public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;

    public void Dispose()
    {
        _ = _hubConnection.DisposeAsync();
    }
}

Création d'un composant SMS entrant

Ensuite, nous allons construire le InboundSms Nous allons commencer de la même manière que pour le composant DeliveredMessages en créant un composant razor appelé InboundSms et y ajouter les dépendances.

@using Microsoft.AspNetCore.SignalR.Client
@using VonageSmsDashboard.Shared
@inject NavigationManager NavigationManager
@inject HttpClient Http
@implements IDisposable

Nous allons ensuite y ajouter un tableau très similaire, qui affichera les données de notre webhook.

<div class="x-display-table">
<h2>Received MessagesMessages</h2>

<table class="table" id="messageList">
    <thead>
        <tr>
            <th>From</th>
            <th>To</th>
            <th>Time</th>
            <th>Message Id</th>
            <th>Message</th>
        </tr>
    </thead>
    <tbody>
        @for (var i = _messages.Count - 1; i >= 0; i--)
        {
            var message = _messages[i];
        <tr>
            <td>@message.Msisdn</td>
            <td>@message.To</td>
            <td>@message.MessageTimestamp</td>
            <td>@message.MessageId</td>
            <td>@message.Text</td>
        </tr>
        }
    </tbody>

</table>
</div>

Enfin, nous mettrons à jour notre bloc de code pour récupérer les messages entrants lorsque le composant s'initialise et configure la connexion au concentrateur. Lorsque le composant se finalise, il se débarrasse de la connexion au concentrateur.

@code {
    private HubConnection _hubConnection;
    private List<InboundSmsModel> _messages = new List<InboundSmsModel>();

    protected override async Task OnInitializedAsync()
    {
        _hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/SmsHub"))
            .Build();
        _hubConnection.On<InboundSmsModel>("ReceiveMessage", (sms) =>
        {
            _messages.Add(sms);
            StateHasChanged();
        });
        await _hubConnection.StartAsync();
        _messages = await Http.GetFromJsonAsync<List<InboundSmsModel>>("/sms/getinboundsms");
    }

    public bool IsConnected => _hubConnection.State == HubConnectionState.Connected;

    public void Dispose()
    {
        _ = _hubConnection.DisposeAsync();
    }
}

Construire le composant SendSms

Il nous faut maintenant construire l'interface pour l'envoi de SMS. Nous allons créer un composant séparé que nous appellerons MessageSenderPour cela, créez un composant portant ce nom, injectez un HttpClient et incluez le projet partagé.

@inject HttpClient Http
@using System.Text.Json
@using VonageSmsDashboard.Shared

Ensuite, nous allons lier un objet OutboundSms à trois champs de saisie, a to, from, et text champ. De plus, nous allons afficher notre dernier message envoyé id existe. Nous l'afficherons.

To:
<input id="to" @bind="@Message.To" placeholder="To Number" class="input-group-text" />
From:
<input id="from" @bind="@Message.From" placeholder="From Number" class="input-group-text" />
Text:
<input id="text" @bind="@Message.Text" placeholder="Text" class="input-group-text" />
<br />
<button class="btn btn-primary" @onclick="SendSms">Send SMS</button>

@if (LastMessageId != null)
{
    <br />
    <h2>Most Recently Sent Message: @LastMessageId</h2>

}

Enfin, nous devons envoyer le message. Nous allons envoyer une requête à notre contrôleur, puis extraire l'identifiant du message de la réponse et le stocker dans le fichier LastMessageId.

@code {
    OutboundSms Message { get; set; } = new OutboundSms();
    string LastMessageId { get; set; }

    private async Task SendSms()
    {
        var response = await Http.PostAsJsonAsync<OutboundSms>("/sms/sendsms", Message);
        var json = await response.Content.ReadAsStringAsync();
        LastMessageId = JsonSerializer.Deserialize<OutboundSms>(json).MessageId;
    }
}

Mise en page du Frontend

Maintenant, mettons en place le frontend. Heureusement, nous avons tout divisé en composants, il nous suffit donc d'inclure le composant VonageSmsDashboard.Client.Components dans le fichier index.razor et d'ajouter les trois nouveaux composants dans une div.

@page "/"
@using VonageSmsDashboard.Client.Components

    <div style="height: 100%">
        
        
        
    </div>

Ajouter du style

Ajoutons un peu de style au fichier wwwroot\css\app.css pour que le rendu soit un peu plus agréable :

.x-display-table {
    width: 90%;
    max-height: 400px;
    overflow-y: auto;
}

Configurer les Webhooks

La dernière chose à faire avant de tester est de configurer nos webhooks. Nous devons faire pointer les webhooks vers les points de terminaison sur lesquels nous voulons recevoir nos messages entrants et nos DLR. Plus tôt, nous avons exécuté une commande ngrok qui a chargé ngrok dans http://fb09abd3c106.ngrok.io - la chaîne aléatoire avant ngrok.io sera différent. Le chemin vers lequel nous devons pointer pour nos SMS entrants est http://fb09abd3c106.ngrok.io/webhooks/inbound-sms et http://fb09abd3c106.ngrok.io/webhooks/dlr pour les messages DLR. Nous devons maintenant définir ces URL et définir la méthode HTTP à POST-JSON dans le tableau de bord

Configuring the webhook settings in the Vonage dashboardConfiguring the webhook settings in the Vonage dashboard

Configurer l'application

La dernière chose que nous devons faire avant de tester est d'ajouter notre API_KEYet API_SECRET à la configuration. Ouvrez VonageSmsDashboard.Server\appsettings.json et ajoutez-y votre clé d'API et votre secret d'API, votre configuration ressemblera à quelque chose comme :

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "API_KEY": "API_KEY",
  "API_SECRET": "API_SECRET"
}

Port de mise à jour

Nous avons également supposé, lorsque nous avons configuré ngrok, que nous écouterions sur le port 5000, vous pouvez le faire en ouvrant le fichier properties\launchsettings.json et en changeant le applicationUrl en http://localhost:5000 pour IIS express, si vous utilisez IIS express, et changez le port SSL à 0. Ou, si vous utilisez Kestrel, vous pouvez supprimer le point de terminaison https://localhost:5001 point de terminaison.

Test

Vous êtes maintenant prêt à tester. Lancez l'application dans IIS Express ou Kestrel, et c'est parti. Vous verrez un tableau de bord qui ressemble à ceci :

Example of what the final dashboard will look likeExample of what the final dashboard will look like

Conclusion

C'est incroyable ce que la combinaison de Blazor et des API de Vonage peut nous permettre de faire avec un peu de HTML et un soupçon de code C#. En résumé, nous avons construit une application SPA riche en fonctionnalités avec ZERO JavaScript en quelques minutes.

Ressources

  • Le code de cette démo se trouve dans GitHub

  • Si vous regardez mes autres articles sur le blogue des développeurs de Vonage vous verrez toutes sortes d'autres exemples vraiment intéressants sur la façon d'utiliser .NET et les API de Vonage ensemble.

Partager:

https://a.storyblok.com/f/270183/384x384/73d57fd8eb/stevelorello.png
Steve LorelloAnciens de Vonage

Ancien développeur .NET Advocate @Vonage, ingénieur logiciel polyglotte full-stack, AI/ML