
Compartir:
Antiguo desarrollador .NET Advocate @Vonage, ingeniero de software poliglota full-stack, AI/ML
Crear un panel de SMS con Blazor WebAssembly
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.
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.
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:
Abra Visual Studio
Haga clic en Crear nueva aplicación
Seleccione Blazor App
Nombra tu aplicación VonageSmsDashboard
Haga clic en Crear
Seleccione Blazor WebAssembly App
Marque la casilla ASP.NET Core hosted en la parte inferior derecha y en la esquina
Haga clic en Crear
Create a new Blazor Application
Después de crear tu proyecto, vas a tener una solución con tres proyectos:
VonageSmsDashboard.Client
VonageSmsDashboard.Server
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 updateEsto 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 IDisposableA 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.SharedA 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 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 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.
