
Compartilhar:
Steve é um ex-membro da equipe da Vonage. Ele atuou como Developer Advocate .NET na Vonage, engenheiro de software full-stack poliglota, especializado em IA/ML
Crie um painel de controle de SMS com o Blazor WebAssembly
Tempo de leitura: 9 minutos
Se você tem acompanhado meu feed no Twitter, talvez tenha percebido que venho desenvolvendo alguns aplicativos de demonstração com SMS em frentes paralelas, com Blazor e MVC puro.
Existem três funcionalidades principais que precisamos levar em consideração quando se trata de SMS.
Recebimento de comprovantes de entrega as mensagens SMS enviadas
Agora que já resolvemos tudo isso, pareceu-me um bom momento para fazer uma pausa e criar algo mais complexo. É isso que vamos fazer neste tutorial.
Objetivos
Vamos usar Blazor WebAssembly, MVC, o Entity Framework Coree o SignalR para criar um aplicativo de página única (SPA).
Este aplicativo será capaz de enviar mensagens SMS, é claro. Ele também poderá receber mensagens SMS; cada mensagem recebida será armazenada usando o Entity Framework e enviada diretamente para nossos aplicativos clientes por meio do SignalR. Ele fará o mesmo com todos os comprovantes de entrega que receber. Esse projeto pode parecer um pouco ambicioso, mas garanto que não é. Então, sem mais delongas, vamos começar nossa aventura.
Ir direto para o código
Se você quiser ver o código desta demonstração, pode conferi-lo no GitHub.
Pré-requisitos
Vamos precisar do Visual Studio 2019 para usar o Blazor WebAssembly
Vamos usar o Ngrok para os testes
Executar o Ngrok
Vou colocar tudo no localhost:5000; rodar o ngrok vai nos permitir acessar publicamente localhost:5000.
Anote a URL em que o ngrok está sendo executado. No meu caso, ele está sendo executado em http://fb09abd3c106.ngrok.io. Essa URL será a URL base para meus webhooks daqui em diante.
Crie sua solução
Vamos criar nossa solução:
Abra o Visual Studio
Clique em “Criar novo aplicativo”
Selecione o aplicativo Blazor
Nomeie seu aplicativo: VonageSmsDashboard
Clique em “Criar”
Selecionar aplicativo Blazor WebAssembly
Marque a caixa “Hospedado no ASP.NET Core” no canto inferior direito
Clique em “Criar”
Create a new Blazor Application
Depois de criar seu projeto, você terá uma solução com três projetos:
VonageSmsDashboard.Client
VonageSmsDashboard.Server
VonageSmsDashboard.Shared
Instalar dependências
Você precisará adicionar os seguintes pacotes NuGet aos seus respectivos projetos.
VonageSmsDashboard.Server
Vonage
Microsoft.AspNetCore.SignalR.Core
Microsoft.EntityFrameworkCore.Sqlite
Microsoft.EntityFrameworkCore.Design VonageSmsDashboard.Client
Microsoft.AspNetCore.SignalR.Client VonageSmsDashboard.Shared
System.ComponentModel.Annotations Construa nosso modelo
Vamos usar o EntityFramework Core com o SQLite para hospedar nosso banco de dados. Seria possível simplesmente usar as estruturas de dados do SDK da Vonage como modelo, mas, como estamos usando WebAssembly, isso implicaria enviar todo o SDK do Nexmo em C# para o navegador do cliente, o que quadruplicaria o tamanho do WASM. Para evitar isso, vamos adicionar Plain Old CLR Objects (POCOs) ao nosso VonageSmsDashboard.Shared projeto. Adicione as seguintes classes a esse projeto:
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; }
} Criar migração e banco de dados
Mude para VonageSmsDashboard.Server e adicione o arquivo SmsContext.cs ao projeto. Adicione um DbSet para nossas mensagens recebidas e Dlrs. Em seguida, sobrescreva o OnConfiguring método para apontar para uma fonte de dados 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");Crie seu projeto rapidamente, ou dotnet ef corre o risco de remover seus pacotes dependentes ao realizar a migração. Agora, no seu console, acesse o VonageSmsDashboard.Server e execute o seguinte:
dotnet tool install --global dotnet-ef
dotnet ef migrations add InitialCreate
dotnet ef database updateIsso resultará em Migrations\SmsContextModelSnapshot.cs e Migrations\A_TIMESTAMP_InitialCreate.cs depois disso, com o modelo criado, vamos agora adicionar nosso SignalR Hub.
Adicionar SMS Hub
No nosso VonageSmsDashboard.Server projeto, adicione uma pasta chamada Hubs, depois adicione um SmsHub.cs arquivo nessa pasta. Faça com que o SmsHub amplie Hub.
public class SmsHub : Hub{} Configurar o middleware
Vamos precisar configurar alguns componentes de middleware para nos ajudar a dar os primeiros passos com o Entity/SignalR.
Em Startup.cs encontre o ConfigureServices e adicione as duas linhas a seguir a ele:
services.AddSignalR();
services.AddDbContext<SmsDashboardContext>();Em seguida, no Configure método, adicione um endpoints.MapHub("/smshub") ao UseEndpoints delegado, que agora ficará assim:
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
endpoints.MapHub<Hubs.SmsHub>("/smshub");
}); Controlador de compilação
Com nosso modelo criado e todo o nosso middleware configurado, vamos criar nosso controlador. Em VonageSmsDashboard.Server clique com o botão direito do mouse na Controllers pasta -> Adicionar -> Controlador -> API Controller - Empty -> Adicionar -> SmsController.cs
Injetar dependências
Vamos injetar nossas dependências no controlador SMS. Vamos precisar de um IConfiguration para extrair nosso API_KEY/API_SECRET. Precisaremos de um IHubContext para gerenciar a conexão com nossos clientes. E vamos precisar de um SmsContext que servirá de acesso ao nosso banco de dados. Injetem todas elas no construtor do controlador desta forma:
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;
} Adicionar ação “SendSms”
Agora vamos adicionar uma ação para enviar um SMS; ela receberá um objeto OutboundSms, obterá nossa ApiKey e nosso segredo da configuração, enviará um SMS e, em seguida, adicionará o ID da mensagem, o preço e o carimbo de data/hora ao nosso objeto de solicitação, retornando-o ao 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;
} Adicionar métodos de leitura ao modelo
A seguir, vamos adicionar métodos `Get` para cada um dos nossos tipos de modelo, que simplesmente irão lê-los do banco de dados e retorná-los ao 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();
} Adicionar rota para lidar com SMS recebidos e DLR
Agora precisamos de uma rota para processar as mensagens SMS e DLR recebidas em nosso aplicativo. Essas solicitações extrairão o corpo do fluxo e o deserializarão em um objeto Inbound SMS e DLR. Em seguida, mapearão os campos essenciais para nossos objetos de modelo. Por fim, enviarão os dados aos clientes do SmsHub. Por fim, salvará o novo SMS de entrada/DLR em nosso banco de dados.
[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();
} Criar o front-end
A última coisa que precisamos fazer é construir o front-end. Normalmente, isso envolveria juntar HTML/JS/CSS até deixarmos tudo do jeito que queremos. A beleza do Blazor WASM é que precisamos nos preocupar apenas com um tipo de unidade: nossos componentes Razor. Vamos criar uma Components pasta no VonageSmsDashboard.Client diretório.
Desativar o armazenamento em cache
Primeiro, vamos precisar carregar elementos do nosso controlador dinamicamente; por isso, precisaremos desativar o armazenamento em cache do HttpClient. Abra Program.cs e substitua a builder.Services.AddTransient chamada 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;
});
Criar um componente DeliveredMessage
Na VonageSmsDashboard.Client\Components pasta, adicione um novo componente Razor chamado “DeliveredMessages”; é aqui que vamos exibir as DLRs à medida que elas chegam ao nosso aplicativo. No início desse componente, adicione as instruções “using” para o Microsoft.AspNetCore.SignalR.Client e VonageSmsDashboard.Shared namespaces. Em seguida, injete um NavigationManager e um HttpClient. Além disso, como vamos usar um HubConnection para gerenciar as notificações push do SignalR, precisaremos implementar IDisposable para limpá-lo posteriormente.
@using Microsoft.AspNetCore.SignalR.Client
@using VonageSmsDashboard.Shared;
@inject NavigationManager NavigationManager
@inject HttpClient Http
@implements IDisposable Adicionar um cabeçalho e uma tabela
Agora que já temos nossas dependências, vamos adicionar a parte visual do nosso componente. Será apenas uma tabela preenchida com a lista de mensagens que receberemos do 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>
Adicionar a lógica de inicialização e a conexão com o SignalR
Agora precisamos adicionar alguns campos _hubConnection e _messages para armazenar nossa conexão com o hub do SignalR e as mensagens DLR, respectivamente. Precisamos adicionar uma sobreposição para OnInitializedAsync para inicializar o HubConnection com um delegado que atualizará a lista de mensagens e fará uma atualização inicial da lista de mensagens com os DLRs disponíveis no momento. Também precisaremos adicionar um Dispose método para descartar o HubConnection quando o componente for finalizado. Coloque tudo isso no bloco de código, que deve ficar assim:
@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();
}
} Criar o componente de SMS recebidas
A seguir, vamos criar o InboundSms Componente — vamos começar da mesma forma que começamos o DeliveredMessages Componente: vamos criar um componente Razor chamado InboundSms e adicionaremos as dependências a ele.
@using Microsoft.AspNetCore.SignalR.Client
@using VonageSmsDashboard.Shared
@inject NavigationManager NavigationManager
@inject HttpClient Http
@implements IDisposableEm seguida, vamos adicionar uma tabela com aparência muito semelhante a ela, que exibirá os dados do nosso 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 fim, vamos atualizar nosso bloco de código para buscar as mensagens recebidas quando o componente for inicializado e configurar a conexão com o hub. Quando o componente for encerrado, ele encerrará a conexão com o 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();
}
} Criar o componente SendSms
Agora precisamos desenvolver o front-end para o envio de mensagens SMS. Vamos criar um componente separado, que chamaremos de MessageSender; vá em frente e crie um componente com esse nome, injete um HttpClient e inclua o projeto compartilhado.
@inject HttpClient Http
@using System.Text.Json
@using VonageSmsDashboard.SharedA seguir, vamos vincular um OutboundSms objeto a três campos de entrada, um to, frome text campo. Além disso, exibiremos o ID da nossa última mensagem enviada, caso ele exista. Vamos mostrá-lo.
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 fim, precisamos enviar a mensagem. Vamos enviar uma solicitação ao nosso controlador, extrair o ID da mensagem da resposta e armazená-lo em 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;
}
} Criar o layout do front-end
Agora vamos montar o front-end. Felizmente, dividimos tudo em componentes, então só precisamos incluir o VonageSmsDashboard.Client.Components no index.razor arquivo e adicionar os três novos componentes juntos em uma div.
@page "/"
@using VonageSmsDashboard.Client.Components
<div style="height: 100%">
</div> Dê um toque de estilo
Vamos dar um toque de estilo ao wwwroot\css\app.css arquivo para que a exibição fique um pouco mais bonita:
.x-display-table {
width: 90%;
max-height: 400px;
overflow-y: auto;
} Configurar Webhooks
A última coisa que precisamos fazer antes de testar é configurar nossos webhooks. Devemos direcionar os webhooks para os endpoints nos quais queremos receber nossas mensagens de entrada e DLRs. Anteriormente, executamos um comando ngrok que carregou o ngrok em http://fb09abd3c106.ngrok.io - a sequência aleatória antes ngrok.io será diferente. O caminho para o qual precisamos direcionar nossas mensagens SMS recebidas é http://fb09abd3c106.ngrok.io/webhooks/inbound-sms e http://fb09abd3c106.ngrok.io/webhooks/dlr para as mensagens DLR. Agora precisamos definir essas URLs e configurar o método HTTP como POST-JSON no painel de controle
Configuring the webhook settings in the Vonage dashboard
Configurar o aplicativo
A última coisa que precisamos fazer antes de testar é adicionar nosso API_KEYe API_SECRET à configuração. Abra VonageSmsDashboard.Server\appsettings.json e adicione sua chave de API e seu segredo de API; sua configuração ficará mais ou menos assim:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"API_KEY": "API_KEY",
"API_SECRET": "API_SECRET"
} Atualizar porta
Ao configurar o ngrok, também partimos do princípio de que estaríamos escutando na porta 5000; você pode definir isso abrindo o properties\launchsettings.json arquivo e alterando o applicationUrl para http://localhost:5000 para o IIS Express, caso esteja usando o IIS Express, e altere a porta SSL para 0. Ou, se estiver usando o Kestrel, você pode remover o https://localhost:5001 ponto de extremidade.
Teste
Agora você já está pronto para testar. Vá em frente e inicie o aplicativo no IIS Express ou no Kestrel, e pronto, está tudo pronto para começar. Você verá um painel de controle mais ou menos assim:
Example of what the final dashboard will look like
Conclusão
É incrível o que a combinação do Blazor com as APIs da Vonage nos permite fazer com um pouco de HTML e um toque de código C#. Para recapitular, criamos um aplicativo SPA repleto de recursos com ZERO JavaScript em poucos minutos.
Recursos
O código desta demonstração está disponível em GitHub
Se você der uma olhada nos meus outros artigos no Blog do Desenvolvedor da Vonage , você vai encontrar vários outros exemplos bem legais de como usar o .NET e as APIs da Vonage em conjunto.