WPF Notifications for all (SignalR)

0
114

Introduction

In the current times, all operating systems and browsers have a notification engine. This is a very practical characteristic and that enables us to have apps 100% connected and 100% lives. With notifications, we can have information of: our others apps, our environments, our users, ours fails, etc., at the same time when they occur.

SignalR is a library for developers that make developing real-time functionality very easy with fantastic results.

I have developed a complete .NET solution with a custom notifications system. This solution brings us the possibility to add Notifications to our environments.

A general graphic example:

This project is open source, and is available in my Git Hub Repository.

The solution has … important projects:

  • MLNotification.Service .- SignalR selfhosting project, with the communication service. This app will be installed in a remote server and will be public in a remote address. The others apps and users will be connected to it for sending and receiving messages.
  • MLNotification.WPFClient .- Principal client app connected to communication service. This app will be installed in the user machines, and will show the notifications in real time.
  • MLNotification.ServiceClientConnect.- This project wrapper the connection service functionality for two ways: sends and receives messages.
  • MLNotification.Domain.- Shared the principal types across of solution.

The other projects are client tests.

MLNotifications is open source, and you can download of this article or in GitHub.

Service Project

It is a classical self hosting SignalR service and these are the principals classes:

MessageHub, 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()
 {
 
 }

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

 }
}

We will show a wrapper class of this class in the project MLNotification.ServiceClientConnect to facilitate the work with the server.

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

Is a WPF application in MVVM, and is connected to service all time. It has two principal parts:

This is the NotifyIcon + Tooltip.

It’s a notify Icon, show the popup notification message (if the notification grid is closed, but this behavior can be set up) and open the surface grid notifications. It has a context menu with two options, the first is a shortcut to configuration and the second close the app.  For this part, I have used the fantastic library WPF NotifyIcon of Philipp Summi developer, I recommend it very much.

The second part is the surface grid notifications:

The surface grid notifications, is a bag for show the notifications messages. The notifications messages can be closed one to one or clean all. In the button of control is an access to configuration that we will cover after.

Notifications Types

There are 8 notifications types, these are divided in 2 groups: less important (simple) and important messages (urgent). Inside each group there are 4 types: Information, warning, error and Very Important.

https://www.youtube.com/watch?v=4Q2UogAvs3g&feature=youtu.be

MLNotification.WPFClient Code Project

The WPF project contains 4 groups. The image speaks for itself. For more details view the classes in the download project.

Settings

In the Settings windows, we will configure 2 principal application parts:

  • Service
    • Service Address .- Is the http address where we will expose the service.
  • Balloon Messages
    • Visibility Time (Seconds) .- This option sets the time balloon messages will be visible.
    • Show Balloon with notifications open .- If this property is activated, the balloons messages always will be displayed, in other case, the balloons messages only show with the Notifications panel closed.

The information settings will be saved in the user Isolated Storage, and reconnect the server for each saved.

If you misconfigure the address setting, the panel settings show the connect error:

https://www.youtube.com/watch?v=j1UsfiDSd04&feature=youtu.be

NotificationMessage Class

NotificationMessage class is a very important class in the solution. This class travel across of service and the clients and contains the message information.

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
 }

Your properties names explain themselves.

MessageType covers all cases showed previously.

MLNotification.ServiceClientConnect

This is a server connection wrapper. ServiceClientConnect tries to facilitate the communication between the server and the client, removing the dynamic approach for a strongly typed.

MLMessageHubConect is its principal class.

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

This class contains.

Properties and fields:

  • conexionHub y proxyHub .- Objects injected to connect with SignalR server.
  • userInfo .- Stores the session information.
  • NotificationMessageStr, SendMessageStr and RegisterUserStr.- They contain a consts strings of calls to SignalR server, because this calls are dynamic and are calls with a string parameter.

Events:

  • ProcessMessage .- The ProcessMessage event fire when the server communicates the message input.

Methods:

  • Conect .- Connect with the SignalR server and enabled the responses messages.
  • SendMessage .- Send message to SignalR server.
  • RegisterUser .- Register user in the SignalR server.

Making a WPF Client

Let’s build a WPF application client that connect with the SignalR console service, and send custom messages. We will use the fantastic wpf guid library, MahApps.

 We will install the Microsoft.AspNet.SignalR.Client nuget:

Add references to MLNotifications.Domain and MLNotifications.ServiceClientConnect.

In the xaml part of the MainWindow, we will create a simple window with the principal’s properties of class MLNotification.Domain.NotificationMessage class.

In the code behind:

We will create a private field with the SignalR wrapper hub.

private MLMessageHubConect connectHub;

In the Loaded window method connect with the signalR console service.

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

In the button send click event, will send the message.

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

This is the all code:

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;

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

}

For more details check the project MLNotification.WPFClient2 in the solution.

There is in the solution a console application client project too.

Inside of our WPF client, we can add a message server listener for hear the notifications server. For this, we add a ListBox and completed the Loaded method:

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

Project Source Code

The source code is very big and I can’t upload the CodeProject server, downloaded of GitHub.

Link to GitHub repository.

LEAVE A REPLY