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

Crear un panel de SMS con Blazor WebAssembly

Publicado el November 5, 2020

Tiempo de lectura: 8 minutos

Si has estado siguiendo mi Twitterte habrás dado cuenta de que he estado creando algunas aplicaciones de demostración con SMS en paralelo, con Blazor y MVC puro.

Hay tres funciones principales que debemos tener en cuenta cuando hablamos de SMS.

  1. Envío de mensajes SMS

  2. Recepción de mensajes SMS

  3. Recepción de acuses de recibo de sus SMS enviados

Ahora que todos los que están fuera del camino, parecía un buen punto para parar y construir algo más complicado. Eso es lo que vamos a hacer en este tutorial.

Objetivos

Vamos a utilizar Blazor WebAssemblyMVC, el Entity Framework Corey SignalR para construir una Single-Page-App(SPA).

Esta aplicación será capaz de enviar mensajes SMS, por supuesto. También podrá recibir mensajes SMS; cada mensaje que reciba se almacenará utilizando Entity Framework y se enviará directamente a nuestras aplicaciones cliente utilizando SignalR. Hará lo mismo con todos los recibos de entrega que reciba. Este proyecto puede parecer un poco ambicioso, pero os prometo que no lo es. Así que sin más preámbulos, vamos a comenzar nuestra aventura.

Ir directamente al código

Si quieres ver el código de esta demo, puedes consultarlo en GitHub.

Requisitos previos

  • Necesitaremos Visual Studio 2019 para poder utilizar Blazor WebAssembly

  • Vamos a utilizar Ngrok para las pruebas

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.

Ejecutar Ngrok

Voy a lanzar todo en localhost:5000ngrok nos permitirá acceder públicamente a localhost:5000.

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

Toma nota de la URL en la que se está ejecutando ngrok. En mi caso, se está ejecutando en http://fb09abd3c106.ngrok.io. Esa URL va a ser la URL base para mis webhooks en el futuro.

Cree su solución

Vamos a crear nuestra Solución:

  1. Abra Visual Studio

  2. Haga clic en Crear nueva aplicación

  3. Seleccione Blazor App

  4. Nombra tu aplicación VonageSmsDashboard

  5. Haga clic en Crear

  6. Seleccione Blazor WebAssembly App

  7. Marque la casilla ASP.NET Core hosted en la parte inferior derecha y en la esquina

  8. Haga clic en Crear

Create a new Blazor ApplicationCreate a new Blazor Application

Después de crear tu proyecto, vas a tener una solución con tres proyectos:

  1. VonageSmsDashboard.Client

  2. VonageSmsDashboard.Server

  3. VonageSmsDashboard.Shared

Instalar dependencias

Deberá añadir los siguientes paquetes NuGet a sus respectivos proyectos.

VonageSmsDashboard.Server

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

VonageSmsDashboard.Client

Microsoft.AspNetCore.SignalR.Client

VonageSmsDashboard.Shared

System.ComponentModel.Annotations

Construir nuestro modelo

Vamos a utilizar EntityFramework Core con SQLite para alojar nuestra base de datos. Podríamos utilizar las estructuras de datos del SDK de Vonage como modelo, pero como estamos utilizando WebAssembly, eso implicaría tener que enviar todo el SDK de Nexmo Csharp al navegador del cliente, lo que supondría cuadruplicar el tamaño del wasm. Para evitar esto, vamos a añadir Plain Old CLR Objects(POCOs) a nuestro proyecto. VonageSmsDashboard.Shared proyecto. Añade las siguientes clases a ese proyecto:

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; }
    }

Crear migración y base de datos

Cambia a VonageSmsDashboard.Server y añada el archivo SmsContext.cs al proyecto. Añada un DbSet para nuestros Mensajes de Entrada y Dlrs. Luego anule el método OnConfiguring para que apunte a una fuente de datos 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");

Construya su proyecto rápidamente, o dotnet ef es probable que elimine tus paquetes dependientes cuando realices la migración. Ahora en tu consola, navega al VonageSmsDashboard.Server y ejecuta lo siguiente:

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

Esto mostrará Migrations\SmsContextModelSnapshot.cs y Migrations\A_TIMESTAMP_InitialCreate.cs después de esto, su modelo está creado, ahora vamos a añadir nuestro Hub SignalR.

Añadir SMS Hub

En nuestro VonageSmsDashboard.Server añadir una carpeta llamada Hubsy añada un archivo SmsHub.cs a esa carpeta. Haz que el SmsHub extienda Hub.

public class SmsHub : Hub{}

Configurar middleware

Vamos a necesitar configurar un par de piezas de middleware para nosotros mismos para ayudarnos a configurar con Entity/SignalR.

En Startup.cs busque el método ConfigureServices y añádele las dos líneas siguientes:

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

A continuación, en el método Configure añada un endpoints.MapHub("/smshub") al UseEndpoints delegado, que ahora tendrá el aspecto siguiente

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

Construir controlador

Con nuestro modelo construido y todo nuestro middleware configurado construiremos nuestro controlador. En VonageSmsDashboard.Server click derecho en la carpeta Controllers carpeta -> add -> Controller -> API Controller - Empty -> Añadir -> SmsController.cs

Inyectar dependencias

Vamos a inyectar nuestras dependencias en el controlador de SMS. Vamos a necesitar un IConfiguration para extraer nuestro controlador API_KEY/API_SECRET. Necesitaremos un IHubContext para gestionar la conexión con nuestros clientes. Y vamos a necesitar un SmsContext que será nuestro acceso a nuestra base de datos. Inyéctalos todos en el constructor del Controlador así:

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;
}

Añadir acción SendSms

Ahora añadiremos una acción para enviar un SMS; esta acción tomará un objeto OutboundSms, extraerá nuestra ApiKey/secret de la configuración, enviará un SMS, añadirá el Id de mensaje, el precio y la marca de tiempo a nuestro objeto de solicitud y lo devolverá al solicitante.

[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;
}

Añadir métodos Get para el modelo

A continuación, vamos a añadir métodos de obtención para cada uno de nuestros tipos de modelo que acaba de leer fuera de la base de datos y devolverlo al solicitante.

[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();
}

Añadir ruta para gestionar SMS entrantes y DLR

Ahora necesitamos una ruta para gestionar los mensajes SMS y DLR entrantes en nuestra aplicación. Estas peticiones extraerán el cuerpo del flujo y lo deserializarán en un objeto SMS y DLR entrantes. A continuación, asignará los campos críticos a nuestros objetos modelo. A continuación, lo enviará a los clientes de SmsHub. Por último, guardará el nuevo inbound/Dlr en nuestra base de datos.

[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();
}

Construir el frontend

Lo último que tenemos que hacer es construir el frontend. Normalmente, esto implicaría juntar HTML/js/CSS hasta que tengamos todo tal y como nos gusta. La belleza de Blazor wasm es que sólo tenemos que pensar en un tipo de unidad - nuestros componentes de la maquinilla de afeitar. Vamos a crear una carpeta Components en el directorio VonageSmsDashboard.Client directorio.

Desactivar caché

En primer lugar, vamos a tener que cargar cosas de nuestro controlador de forma dinámica, por lo que tendremos que desactivar el almacenamiento en caché del archivo HttpClient. Abrir Program.cs y sustituir la llamada builder.Services.AddTransient por :

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;
});

Crear un componente DeliveredMessage

En la carpeta VonageSmsDashboard.Client\Components añade un nuevo componente razor "DeliveredMessages", aquí es donde vamos a mostrar los DLRs a medida que llegan a nuestra aplicación. En la cabecera de este componente añade sentencias using para los componentes Microsoft.AspNetCore.SignalR.Client y VonageSmsDashboard.Shared para los namespaces. Luego inyecta un NavigationManager y un HttpClient. Además, como vamos a utilizar un HubConnection para gestionar el push desde SignalR, vamos a necesitar implementar IDisposable para limpiarlo después.

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

Añadir una cabecera y una tabla

Ahora que tenemos nuestras dependencias vamos a añadir la parte visual de nuestro componente. Será una tabla rellenada con la lista de mensajes que recibiremos del servidor.

<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>

Añadir lógica de inicialización y conexión SignalR

Ahora debemos añadir un par de campos _hubConnection y _messages para guardar nuestra conexión al concentrador SignalR y los mensajes DLR, respectivamente. Necesitamos añadir un override a OnInitializedAsync para inicializar el HubConnection con un delegado que actualizará la lista de mensajes, y hacer una actualización inicial de la lista de mensajes con los DLRs actualmente disponibles. También tendremos que añadir un método Dispose para deshacernos del HubConnection cuando el componente finalice. Pon todo esto en el bloque de código, que debería tener el siguiente aspecto:

@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();
    }
}

Crear un componente de SMS entrantes

A continuación, construiremos el InboundSms Componente - vamos a empezar de la misma manera que empezamos el DeliveredMessages Componente, crear un componente razor llamado InboundSms y añadir las dependencias a la misma.

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

A continuación vamos a añadirle una tabla de aspecto muy similar, que mostrará los datos de nuestro 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>

Por último, actualizaremos nuestro bloque de código para obtener los mensajes entrantes cuando el componente se inicialice y configure la conexión al hub. Cuando el componente finalice, se deshará de la conexión al hub.

@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();
    }
}

Crear el componente SendSms

Ahora tenemos que construir el frontend para el envío de mensajes SMS. Haremos un componente separado que llamaremos MessageSendery crearemos un componente con ese nombre, inyectaremos un HttpClient e incluiremos el proyecto compartido.

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

A continuación, enlazaremos un objeto OutboundSms a tres campos de entrada, a to, fromy text campo. Además, mostraremos nuestro último mensaje enviado id existe. Lo mostraremos.

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>

}

Por último, debemos enviar el mensaje. Enviaremos una petición a nuestro controlador, extraeremos el Id de mensaje de la respuesta y lo almacenaremos en el fichero 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;
    }
}

Diseño de la interfaz

Ahora vamos a diseñar el frontend. Afortunadamente hemos dividido todo en componentes, por lo que sólo tenemos que incluir el VonageSmsDashboard.Client.Components en el archivo index.razor y añadir los tres nuevos componentes juntos en un div.

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

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

Añade estilo

Añadamos un poco de estilo al archivo wwwroot\css\app.css para que se vea un poco mejor:

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

Configurar Webhooks

Lo último que tenemos que hacer antes de probar es configurar nuestros webhooks. Debemos apuntar los webhooks a los endpoints en los que queremos recibir nuestros mensajes entrantes y DLRs. Anteriormente, ejecutamos un comando ngrok que cargó ngrok en http://fb09abd3c106.ngrok.io - la cadena aleatoria antes de ngrok.io será diferente. La ruta a la que tenemos que apuntar para nuestros mensajes SMS entrantes es http://fb09abd3c106.ngrok.io/webhooks/inbound-sms y http://fb09abd3c106.ngrok.io/webhooks/dlr para los mensajes DLR. Ahora tenemos que establecer esas direcciones URL y establecer el método HTTP a POST-JSON en el dashboard

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

Configurar la aplicación

Lo último que tenemos que hacer antes de hacer la prueba es añadir nuestro API_KEYy API_SECRET a la configuración. Abre VonageSmsDashboard.Server\appsettings.json y añádele tu clave de API y tu secreto de API, tu configuración tendrá un aspecto parecido:

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

Puerto de actualización

También asumimos cuando configuramos ngrok que estaríamos escuchando en el puerto 5000, puedes configurar esto abriendo el archivo properties\launchsettings.json y cambiando applicationUrl a http://localhost:5000 para IIS express, si está utilizando IIS express, y cambie el puerto SSL a 0. O, si está utilizando Kestrel, puede eliminar el endpoint https://localhost:5001 endpoint.

Prueba

Ya está todo listo para la prueba. Sigue adelante y enciende la aplicación en IIS Express o Kestrel, y estarás listo para las carreras. Usted verá un tablero de instrumentos que se parece a esto:

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

Conclusión

Es increíble lo que la combinación de Blazor y las API de Vonage puede permitirnos hacer con un poco de HTML y un toque de código C#. Para repasar, hemos creado una aplicación SPA rica en funciones con CERO JavaScript en minutos.

Recursos

  • El código de esta demostración se encuentra en GitHub

  • Si echa un vistazo a mis otros artículos en el Blog para desarrolladores de Vonage verás todo tipo de ejemplos realmente geniales sobre cómo usar .NET y las API de Vonage juntos.

Compartir:

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

Antiguo desarrollador .NET Advocate @Vonage, ingeniero de software poliglota full-stack, AI/ML