Click here to Skip to main content
15,868,340 members
Articles / Desktop Programming / WPF

WPF Notifications for all (SignalR)

Rate me:
Please Sign up or sign in to vote.
4.33/5 (5 votes)
22 Jun 2017CPOL5 min read 18.5K   15   3
WPF Notifications engine for your environments

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:

Image 1

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

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

The solution has… important projects:

Image 2

  • 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

Image 3

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

MessageHub, a class inherit of Hub:

C#
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
        }
    }
}

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

Program.cs

C#
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

C#
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:

Image 4

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:

Image 5

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

Image 6

Image 7

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

MLNotification.WPFClient Code Project

Image 8

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

Settings

Image 9

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

In the Settings windows, we will configure two 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:

Image 10

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.

C#
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 shown 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.

Image 11

MLMessageHubConect is its principal class.

C#
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 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:

  • Connect - Connect with the SignalR server and enable 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 connects with the SignalR console service, and sends custom messages. We will use the fantastic WPF guid library, MahApps.

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

Image 12

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.

Image 13

In the code behind:

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

C#
private MLMessageHubConect connectHub;

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

C#
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.

C#
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:

C#
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
            }
        }
    }
}

Image 14

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

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 hearing the notifications server. For this, we add a ListBox and completed the Loaded method:

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

Image 15

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

Project Source Code

The source code is very big and I can't upload to the CodeProject server, so below is the GitHub link:

History

  • 22nd June, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Cecabank
Spain Spain
MVP C# Corner 2017

MAP Microsoft Active Professional 2014

MCPD - Designing and Developing Windows Applications .NET Framework 4
MCTS - Windows Applications Development .NET Framework 4
MCTS - Accessing Data Development .NET Framework 4
MCTS - WCF Development .NET Framework 4

Comments and Discussions

 
QuestionDemo Application Pin
NolanUltraThug5930-Oct-21 22:24
NolanUltraThug5930-Oct-21 22:24 
QuestionSolution cannot build and Error to connect to Service Pin
Member 1332549626-Jul-17 2:39
Member 1332549626-Jul-17 2:39 
AnswerRe: Solution cannot build and Error to connect to Service Pin
Juan Francisco Morales Larios6-Aug-17 1:16
Juan Francisco Morales Larios6-Aug-17 1:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.