WPF Notifications For All (SignalR)

These days, all operating systems and browsers have a notification engine. This is a very practical characteristic that enables us to have apps 100% connected and 100% live. With notifications, we can have information about: our other apps, our environments, our users, our fails, etc., when they occur.

SignalR is a library for developers that makes 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

WPF

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

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

  • ServiceClientConnect
    This project wrappers the connection service functionality two ways: It sends and receives messages.

  • Domain
    Shares 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 

  1. using Microsoft.AspNet.SignalR;  
  2. using MLNotification.Domain;  
  3. using System;  
  4. using System.Threading.Tasks;  
  5.   
  6. namespace MLNotification.Service  
  7. {  
  8.     public class MLMessageHub : Hub  
  9.     {  
  10.         async public override Task OnConnected()  
  11.         {  
  12.             // Code for connection to service.  
  13.         }  
  14.         async public override Task OnDisconnected(bool stopCalled)  
  15.         {  
  16.             Console.WriteLine("Nueva conexion con Id=" + Context.ConnectionId);  
  17.   
  18.             var message = new NotificationMessage  
  19.             {  
  20.                 Subject     = "New service desconnection",  
  21.                 Body        = $"There is a desconnection from the UserId:{Context.ConnectionId}",  
  22.                 MessageDate = DateTime.Now,  
  23.                 MessageType = MessageType.Information,  
  24.                 UriImage    = "http://www.tampabay.com/resources/images/dti/rendered/2015/04/wek_plug041615_15029753_8col.jpg"  
  25.             };  
  26.   
  27.             await Clients.Caller.ProcessMessage(message);  
  28.             await Clients.Others.ProcessMessage(message);  
  29.         }  
  30.   
  31.         async public Task SendMessage(NotificationMessage message)  
  32.         {  
  33.             Console.WriteLine("[" + message.User + "]: " + message.Body);  
  34.             await Clients.All.ProcessMessage(message);  
  35.         }  
  36.   
  37.   
  38.         async public Task RegisterUser(UserInfo userInfo)  
  39.         {  
  40.             // Code for register user  
  41.         }  
  42.   
  43.     }  
  44. }   

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

Program.cs 

  1. using Microsoft.Owin.Hosting;  
  2. using System;  
  3.   
  4. namespace MLNotification.Service  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             using (WebApp.Start<Startup>("http://localhost:11111"))  
  11.             {  
  12.                 Console.WriteLine("Hub on http://localhost:11111");  
  13.                 Console.ReadLine();  
  14.             }  
  15.         }  
  16.     }  
  17. }   

Startup.cs 

  1. using Microsoft.Owin.Cors;  
  2. using Owin;  
  3.   
  4. namespace MLNotification.Service  
  5. {  
  6.     public class Startup  
  7.     {  
  8.         public static void Configuration(IAppBuilder app)  
  9.         {  
  10.             app.UseCors(CorsOptions.AllowAll);  
  11.             app.MapSignalR();  
  12.         }  
  13.     }  
  14. }   

Client

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



This is the Notify Icon + Tooltip.

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

The second part is the surface grid notifications,



The surface grid notifications, is a bag for showing the notification messages. The notification 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.

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

 


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 will reconnect the server for each one saved.

If you misconfigure the address setting, the panel settings shows the connection error

NotificationMessage Class

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

  1. using System;  
  2. using System.ComponentModel.DataAnnotations;  
  3.   
  4. namespace MLNotification.Domain  
  5. {  
  6.     [Serializable]  
  7.     public class NotificationMessage : INotificationMessage  
  8.     {  
  9.         [Required]  
  10.         [MaxLength(100)]  
  11.         public string Subject { get; set; }  
  12.   
  13.         [Required]  
  14.         [MaxLength(2000)]  
  15.         public string Body { get; set; }  
  16.   
  17.         public MessageType MessageType { get; set; }  
  18.   
  19.         [MaxLength(50)]  
  20.         public string Group { get; set; }  
  21.   
  22.         [MaxLength(50)]  
  23.         public string User { get; set; }  
  24.   
  25.         [MaxLength(50)]  
  26.         public string Server { get; set; }  
  27.   
  28.         public DateTime MessageDate { get; set; }  
  29.   
  30.         public string UriImage { get; set; }  
  31.     }  
  32.   
  33. }  
  34. namespace MLNotification.Domain  
  35. {  
  36.     public enum MessageType  
  37.     {  
  38.         Information,  
  39.         Warnnig,  
  40.         Error,   
  41.         VeryImportant,  
  42.         Information_urgent,  
  43.         Warnnig_urgent,  
  44.         Error_urgent,  
  45.         VeryImportant_urgent  
  46.     }  
  47. }   

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

 

MLMessageHubConect is its principal class. 
  1. using Microsoft.AspNet.SignalR.Client;  
  2. using MLNotification.Domain;  
  3. using MLNotification.ServiceClientConnect.EventArgs;  
  4. using System;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace MLNotification.ServiceClientConnect  
  8. {  
  9.     public class MLMessageHubConect : IDisposable, IMLMessageHubConect  
  10.     {  
  11.         public  HubConnection conexionHub = null;  
  12.         private IHubProxy     proxyHub    = null;  
  13.   
  14.         public IUserInfo userInfo;  
  15.   
  16.         private const string NotificationMessageStr = "ProcessMessage";  
  17.         private const string SendMessageStr         = "SendMessage";  
  18.         private const string RegisterUserStr        = "RegisterUser";  
  19.   
  20.         public event EventHandler<MLMessageEventArgs> ProcessMessage;  
  21.   
  22.   
  23.   
  24.         public MLMessageHubConect(HubConnection conexionHub, IHubProxy proxyHub, IUserInfo userInfo = null)  
  25.         {  
  26.             this.conexionHub = conexionHub;  
  27.             this.proxyHub    = proxyHub;  
  28.   
  29.             this.userInfo = userInfo;  
  30.   
  31.             Connect();  
  32.   
  33.             RegisterUser(userInfo);  
  34.         }  
  35.   
  36.         private void Connect()  
  37.         {  
  38.             try  
  39.             {  
  40.                 proxyHub.On(NotificationMessageStr, (NotificationMessage message) =>  
  41.                 {  
  42.                     if (message != null && conexionHub != null)  
  43.                     {  
  44.                         OnProcessMessage(message);  
  45.                     }  
  46.                 });  
  47.   
  48.                 Task.WaitAll(conexionHub.Start());  
  49.   
  50.                 RegisterUser(userInfo);  
  51.             }  
  52.             catch (Exception ex)  
  53.             {  
  54.                 System.Diagnostics.Debug.WriteLine("Error " + ex.Message);  
  55.   
  56.                 throw new HubException($"Error to connect to Service. Check the service is online, and the ServiceAddress is correct. Error:{ex.Message}");  
  57.             }  
  58.         }  
  59.   
  60.   
  61.         public Task SendMessage(NotificationMessage message)  
  62.         {  
  63.             return proxyHub.Invoke(SendMessageStr, message);  
  64.         }  
  65.   
  66.         public Task RegisterUser(IUserInfo userInfo)  
  67.         {  
  68.             return proxyHub.Invoke(RegisterUserStr, userInfo);  
  69.         }  
  70.   
  71.         protected internal virtual void OnProcessMessage(NotificationMessage message) => ProcessMessage?.Invoke(thisnew MLMessageEventArgs(message));  
  72.   
  73.   
  74.         public void Dispose()  
  75.         {  
  76.             conexionHub.Dispose();  
  77.             conexionHub = null;  
  78.             proxyHub    = null;  
  79.         }  
  80.     }  
  81. }   

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 string of calls to SignalR server, because these 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. 

  1. private MLMessageHubConect connectHub;   

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

  1. private void MainWindow_Loaded(object sender, RoutedEventArgs e)  
  2. {  
  3.     try  
  4.     {  
  5.         if (connectHub != null) connectHub.Dispose();  
  6.   
  7.         connectHub = BuilderMLMessageHubConnect.CreateMLMessageHub(txtUrl.Text);  
  8.     }  
  9.     catch (Exception ex)  
  10.     {  
  11.         MessageBox.Show(ex.Message);  
  12.     }  
  13. }   

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

  1. async private void btnSend_Click(object sender, RoutedEventArgs e)  
  2. {  
  3.     var message = new NotificationMessage  
  4.     {  
  5.         Subject     = txtSubject.Text,  
  6.         Body        = txtMensaje.Text,  
  7.         User        = txtUser.Text,  
  8.         MessageDate = DateTime.Now,  
  9.         Server      = txtServer.Text,  
  10.         UriImage    = txtUriImage.Text  
  11.     };  
  12.   
  13.     message.MessageType = (MessageType)cmbType.SelectedIndex;  
  14.   
  15.     await connectHub.SendMessage(message);  
  16. }   

This is the all code, 

  1. using MahApps.Metro.Controls;  
  2. using MLNotification.Domain;  
  3. using MLNotification.ServiceClientConnect;  
  4. using System;  
  5. using System.ComponentModel;  
  6. using System.Windows;  
  7. using System.Windows.Controls;  
  8. using System.Windows.Media;  
  9. using System.Windows.Media.Imaging;  
  10.   
  11.   
  12. namespace MLNotification.WPClient2  
  13. {  
  14.   
  15.     public partial class MainWindow : MetroWindow  
  16.     {  
  17.   
  18.         private MLMessageHubConect connectHub;  
  19.   
  20.   
  21.   
  22.         public MainWindow()  
  23.         {  
  24.             InitializeComponent();  
  25.   
  26.             this.AllowsTransparency = true;  
  27.   
  28.             /// enabled drag and drop the window  
  29.             MouseDown += (sender, e) =>  
  30.             {  
  31.                 this.DragMove();  
  32.                 e.Handled = false;  
  33.             };  
  34.   
  35.             Loaded += MainWindow_Loaded;  
  36.         }  
  37.   
  38.         private void MainWindow_Loaded(object sender, RoutedEventArgs e)  
  39.         {  
  40.             try  
  41.             {  
  42.                 if (connectHub != null) connectHub.Dispose();  
  43.   
  44.                 connectHub = BuilderMLMessageHubConnect.CreateMLMessageHub(txtUrl.Text);  
  45.             }  
  46.             catch (Exception ex)  
  47.             {  
  48.                 MessageBox.Show(ex.Message);  
  49.             }  
  50.         }  
  51.   
  52.   
  53.         async private void btnEnviar_Click(object sender, RoutedEventArgs e)  
  54.         {  
  55.             var message = new NotificationMessage  
  56.             {  
  57.                 Subject     = txtSubject.Text,  
  58.                 Body        = txtMensaje.Text,  
  59.                 User        = txtUser.Text,  
  60.                 MessageDate = DateTime.Now,  
  61.                 Server      = txtServer.Text,  
  62.                 UriImage    = txtUriImage.Text  
  63.             };  
  64.   
  65.             message.MessageType = (MessageType)cmbType.SelectedIndex;  
  66.   
  67.             await connectHub.SendMessage(message);  
  68.         }  
  69.   
  70.         protected override void OnClosing(CancelEventArgs e)  
  71.         {  
  72.             connectHub.Dispose();  
  73.   
  74.             base.OnClosing(e);  
  75.         }  
  76.   
  77.   
  78.         private void ButtonClose_Click(object sender, RoutedEventArgs e) => Close();  
  79.   
  80.         private void txtUriImage_TextChanged(object sender, TextChangedEventArgs e)  
  81.         {  
  82.             try  
  83.             {  
  84.                 if (string.IsNullOrWhiteSpace(txtUriImage?.Text)) return;  
  85.   
  86.                 BitmapImage logo = new BitmapImage();  
  87.                 logo.BeginInit();  
  88.                 string path = txtUriImage.Text;  
  89.                 logo.UriSource = new Uri(path, UriKind.RelativeOrAbsolute);  
  90.                 logo.EndInit();  
  91.   
  92.                 var img = new ImageBrush(logo);  
  93.   
  94.                 bdImage.Background = img;  
  95.             }  
  96.             catch (Exception)  
  97.             {  
  98.                 // nothing  
  99.             }  
  100.         }  
  101.     }  
  102.   
  103.   
  104. }   
 

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, 

  1. private void MainWindow_Loaded(object sender, RoutedEventArgs e)  
  2. {  
  3.     try  
  4.     {  
  5.         if (connectHub != null) connectHub.Dispose();  
  6.   
  7.         connectHub = BuilderMLMessageHubConnect.CreateMLMessageHub(txtUrl.Text);  
  8.   
  9.         connectHub.ProcessMessage += (sender2, e2) => lstServerMessages.Dispatcher.Invoke(() =>  
  10.         {  
  11.             lstServerMessages.Items.Add(e2.NotificationMessage.Body);  
  12.         }, System.Windows.Threading.DispatcherPriority.Background);  
  13.     }  
  14.     catch (Exception ex)  
  15.     {  
  16.         MessageBox.Show(ex.Message);  
  17.     }  
  18. }  



Project Source Code

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

Link to GitHub repository.

Up Next
    Ebook Download
    View all
    Learn
    View all