En los tiempos que corren, todos los sistemas operativos e
incluso los navegadores, contienen un sistema de notificaciones. Esta es una característica
muy práctica y nos habilita la posibilidad de tener aplicaciones 100%
conectadas y 100% vivas. Con las notificaciones, podemos tener información
referente a otras aplicaciones de nuestro entorno, a usuarios, errores, etc.,
en el mismo momento en el que ocurren.
SignalR es una librería para desarrolladores que hace fácil
la característica y funcionalidad del tiempo real con unos resultados
fantásticos.
He desarrollado una solución .NET completa de notificaciones
propias para nuestros sistemas. La solución nos brinda la posibilidad de añadir
fácilmente un sistema de notificaciones a nuestros entornos.
Ejemplo gráfico de la solución:
El projecto es completamente de código abierto y lo tenéis
disponible en mi repositorio
de Git Hub.
La solución contiene 4 importantes proyectos:
- MLNotification.Service.- Es un servicio selfhosting que contiene la funcionalidad de la comunicación entre procesos. Está aplicación debería ser instalada (Convertida en servicio Windows normalmente) en un servidor remoto, publicado en una ip pública. Las demás aplicaciones satélite se conectarán a este servicio e enviarán y recibirán mensajes a través de él.
- MLNotification.WPFClient.- Es la aplicación cliente principal conectada al servicio. Esta aplicación debe ser instalada en todas las máquinas de usuarios y mostrará todas las notificaciones en tiempo real.
- MLNotification.ServiceClientConnect.- Es un proyecto que envuelve la funcionalidad de conexión entre los clientes y el servicio, para hacer más fácil su consumo. Además transforma las características dinámicas de SignalR en tipadas, para un mejor control en tiempo de compilación.
- MLNotification.Domain.- Comparte los principales tipos de datos entre todos los proyectos.
Los otros proyectos son simplemente proyectos de pruebas y
testeo.
Proyecto
Service
Es un clásico servicio selfhosting de SignalR.
It is a classical
self hosting SignalR service y sus principales clases son:
A class
inherit of Hub:
using Microsoft.AspNet.SignalR; using MLNotification.Domain; using System; using System.Threading.Tasks; namespace MLNotification.Service { public class MLMessageHub : Hub { async public override Task OnConnected() { // Code for connection to service. } async public override Task OnDisconnected(bool stopCalled) { Console.WriteLine("Nueva conexion con Id=" + Context.ConnectionId); var message = new NotificationMessage { Subject = "New service desconnection", Body = $"There is a desconnection from the UserId:{Context.ConnectionId}", MessageDate = DateTime.Now, MessageType = MessageType.Information, UriImage = "http://www.tampabay.com/resources/images/dti/rendered/2015/04/wek_plug041615_15029753_8col.jpg" }; await Clients.Caller.ProcessMessage(message); await Clients.Others.ProcessMessage(message); } async public Task SendMessage(NotificationMessage message) { Console.WriteLine("[" + message.User + "]: " + message.Body); await Clients.All.ProcessMessage(message); } async public Task RegisterUser(UserInfo userInfo) { // Code for register user } } }
Como veremos más adelante, una clase envoltorio dentro del Proyecto
MLNotifications.ServiceClientConnect nos facilitará el trabajo de conexión con
el servidor.
Program.cs:
using Microsoft.Owin.Hosting; using System; namespace MLNotification.Service { class Program { static void Main(string[] args) { using (WebApp.Start<Startup>("http://localhost:11111")) { Console.WriteLine("Hub on http://localhost:11111"); Console.ReadLine(); } } } }
Startup.cs:
using Microsoft.Owin.Cors; using Owin; namespace MLNotification.Service { public class Startup { public static void Configuration(IAppBuilder app) { app.UseCors(CorsOptions.AllowAll); app.MapSignalR(); } } }
Client
Es una aplicación de WPF con MVVM y está conectada al
servicio en todo momento. Tiene 2 partes principales:
Estas partes son el NotifyIcon + ToolTip.
El NotifyIcon, muestra el popup the mensajes de notificación
(por defecto si el grid de notificación está cerrado, aunque esto puede ser
configurado) y abre la superficie del grid de notificaciones. Tiene un menú
contextual con dos opciones, la primera es un acceso directo a la configuración
y la segunda cierra la aplicación. Para esta parte he usado la fantástica
librería WPF
NotifyIcon de el desarrollador Philipp Summi, que es mucho más que
recomendable.
La segunda parte es el grid de notificaciones:
El grid de notificaciones, es un repositorio en donde se
muestran los mensajes de notificación. Los mensajes de notificación pueden ser
cerrados de manera individual o limpiados todos a la vez. También tenemos un
acceso a la configuración del programa.
Tipos de Notificaciones
Hay 8 tipos importantes de notificaciones y se dividen en 2
grupos: menos importantes (simples) y más importantes (urgentes). Dentro de
cada grupo hay 4 tipos: Información, warning, error y muy importante.
MLNotification.WPFClient
código del proyecto
El Proyecto de WPF contiene 4 grupos. La imagen habla por sí
misma y es auto descriptiva Para más información tenéis el código del proyecto.
Configuración
En la ventana de configuración, tendremos disponibles 2
partes fundamentales:
- Service
- Service Address .- Es la dirección http donde se expondrá el servicio.
- Balloon Messages
- Visibility Time (Seconds) .- Esta opción mostrará el número de segundos que se mantendrá visible el mensaje balloon.
- Show Balloon with notifications open .- Si está activada, siempre se mostrará el mensaje balloon, aunque el grid de notificaciones esté abierto, si está desactivada, el mensaje balloon solo se mostrará en caso de estar cerrado el grid de notificaiones.
La información de la configuración se salva en el Isolated
Sotrage del usuario, y el servicio detecta si se ha modificado la dirección y
vuelve a conectar en ese caso.
Si añadimos una dirección no correcta o en la cual no esté
escuchando ningún servicio, se mostrará un error:
Clase NotificationMessage
La clase NotificationMessage es muy importante dentro de la
solución. Los objetos de esta clase viajan a través del servicio a los clientes
y viceversa, además contiene la información del mensaje.
using System; using System.ComponentModel.DataAnnotations; namespace MLNotification.Domain { [Serializable] public class NotificationMessage : INotificationMessage { [Required] [MaxLength(100)] public string Subject { get; set; } [Required] [MaxLength(2000)] public string Body { get; set; } public MessageType MessageType { get; set; } [MaxLength(50)] public string Group { get; set; } [MaxLength(50)] public string User { get; set; } [MaxLength(50)] public string Server { get; set; } public DateTime MessageDate { get; set; } public string UriImage { get; set; } } } namespace MLNotification.Domain { public enum MessageType { Information, Warnnig, Error, VeryImportant, Information_urgent, Warnnig_urgent, Error_urgent, VeryImportant_urgent } }
Sus propiedades se explican por si solas:
MLNotification.ServiceClientConnect
Este Proyecto es un envoltorio que facilita la comunicación
entre el servicio y el cliente.
MLMessageHubConect es su clase principal:
using Microsoft.AspNet.SignalR.Client; using MLNotification.Domain; using MLNotification.ServiceClientConnect.EventArgs; using System; using System.Threading.Tasks; namespace MLNotification.ServiceClientConnect { public class MLMessageHubConect : IDisposable, IMLMessageHubConect { public HubConnection conexionHub = null; private IHubProxy proxyHub = null; public IUserInfo userInfo; private const string NotificationMessageStr = "ProcessMessage"; private const string SendMessageStr = "SendMessage"; private const string RegisterUserStr = "RegisterUser"; public event EventHandler<MLMessageEventArgs> ProcessMessage; public MLMessageHubConect(HubConnection conexionHub, IHubProxy proxyHub, IUserInfo userInfo = null) { this.conexionHub = conexionHub; this.proxyHub = proxyHub; this.userInfo = userInfo; Connect(); RegisterUser(userInfo); } private void Connect() { try { proxyHub.On(NotificationMessageStr, (NotificationMessage message) => { if (message != null && conexionHub != null) { OnProcessMessage(message); } }); Task.WaitAll(conexionHub.Start()); RegisterUser(userInfo); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine("Error " + ex.Message); throw new HubException($"Error to connect to Service. Check the service is online, and the ServiceAddress is correct. Error:{ex.Message}"); } } public Task SendMessage(NotificationMessage message) { return proxyHub.Invoke(SendMessageStr, message); } public Task RegisterUser(IUserInfo userInfo) { return proxyHub.Invoke(RegisterUserStr, userInfo); } protected internal virtual void OnProcessMessage(NotificationMessage message) => ProcessMessage?.Invoke(this, new MLMessageEventArgs(message)); public void Dispose() { conexionHub.Dispose(); conexionHub = null; proxyHub = null; } } }
Esta clase contiene.
Campos y propiedades:
- conexionHub y proxyHub .- Objectos injectados al servicio SignalR.
- userInfo .- Almacena la información de la session del usuario.
- NotificationMessageStr, SendMessageStr and RegisterUserStr.- Estos contienen la información en campos const de los nombres a los métodos dinámicos del hub de SignalR.
Eventos:
- ProcessMessage .- Este evento se lanza cuando el servicio nos comunica mensajes entrantes.
Metodos:
- Conect .- Se conecta con el servicio SignalR y habilita la entrada de mensajes.
- SendMessage .- Envía un mensaje al servidor SignalR.
- RegisterUser .- Registra un usuario en el SignalR.
Haciendo un cliente WPF
Vamos a hacer una aplicación WPF que conecte con el servicio de consola SignalR y envíe mensajes personalizados. Para mejorar su aspecto gráfico, nos apoyaremos en la fantástica librería MahApps.
Instalaremos el paquete de nuget
Microsoft.AspNet.SignalR.Client:
Añadiremos
referencias a MLNotifications.Domain y MLNotifications.ServiceClientConnect.
En la parte del XAML de la MainWindow, crearemos una ventana
simple con cajas de texto para las principales propiedades de la clase
MLNotification.Domain.NotificationMessage.
CodeBehind:
Crearemos un campo privado para la clase envoltorio de
conexión con el hub de SignalR.
private MLMessageHubConect connectHub;
In el evento Loaded, conectaremos con el service de consola SignalR.
private void MainWindow_Loaded(object sender, RoutedEventArgs e) { try { if (connectHub != null) connectHub.Dispose(); connectHub = BuilderMLMessageHubConnect.CreateMLMessageHub(txtUrl.Text); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
En el evento click del botón enviar, enviaremos un message
al servicio.
async private void btnSend_Click(object sender, RoutedEventArgs e) { var message = new NotificationMessage { Subject = txtSubject.Text, Body = txtMensaje.Text, User = txtUser.Text, MessageDate = DateTime.Now, Server = txtServer.Text, UriImage = txtUriImage.Text }; message.MessageType = (MessageType)cmbType.SelectedIndex; await connectHub.SendMessage(message); }
Este es todo el código:
using MahApps.Metro.Controls; using MLNotification.Domain; using MLNotification.ServiceClientConnect; using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; namespace MLNotification.WPClient2 { public partial class MainWindow : MetroWindow { private MLMessageHubConect connectHub; public MainWindow() { InitializeComponent(); this.AllowsTransparency = true; /// enabled drag and drop the window MouseDown += (sender, e) => { this.DragMove(); e.Handled = false; }; Loaded += MainWindow_Loaded; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { try { if (connectHub != null) connectHub.Dispose(); connectHub = BuilderMLMessageHubConnect.CreateMLMessageHub(txtUrl.Text); } catch (Exception ex) { MessageBox.Show(ex.Message); } } async private void btnEnviar_Click(object sender, RoutedEventArgs e) { var message = new NotificationMessage { Subject = txtSubject.Text, Body = txtMensaje.Text, User = txtUser.Text, MessageDate = DateTime.Now, Server = txtServer.Text, UriImage = txtUriImage.Text }; message.MessageType = (MessageType)cmbType.SelectedIndex; await connectHub.SendMessage(message); } protected override void OnClosing(CancelEventArgs e) { connectHub.Dispose(); base.OnClosing(e); } private void ButtonClose_Click(object sender, RoutedEventArgs e) => Close(); private void txtUriImage_TextChanged(object sender, TextChangedEventArgs e) { try { if (string.IsNullOrWhiteSpace(txtUriImage?.Text)) return; BitmapImage logo = new BitmapImage(); logo.BeginInit(); string path = txtUriImage.Text; logo.UriSource = new Uri(path, UriKind.RelativeOrAbsolute); logo.EndInit(); var img = new ImageBrush(logo); bdImage.Background = img; } catch (Exception) { // nothing } } } }
Para más información chequear el proyecto
MLNotification.WPFClient2 en la solución.
La solución también contiene un Proyecto de cliente de
consola.
AÑADIENDO FUNCIONALIDAD DE ESCUCHA DE MENSAJES A NUESTRA
APP.
Dentro de nuestro Proyecto WPF cliente, podemos añaidir la
funcionalidad de escucha de notificaciones. Para esto añadiremos un Listbox y
completaremos el evento Loaded:
private void MainWindow_Loaded(object sender, RoutedEventArgs e) { try { if (connectHub != null) connectHub.Dispose(); connectHub = BuilderMLMessageHubConnect.CreateMLMessageHub(txtUrl.Text); connectHub.ProcessMessage += (sender2, e2) => lstServerMessages.Dispatcher.Invoke(() => { lstServerMessages.Items.Add(e2.NotificationMessage.Body); }, System.Windows.Threading.DispatcherPriority.Background); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
No hay comentarios :
Publicar un comentario