C#: GUI to display realtime messages from Windows Service
Asked Answered
B

8

6

I've written a C# windows service which can write messages to a custom EventLog or to any number of files. These messages are all marked with some priority (so, for example, only ERRORs and WARNINGs get stored in the EventLog, but if desired a lot more can be stored to a file).

What I'd like to do now is create a GUI that can listen for these messages and display them in real-time. Allowing a user to watch the current messages (at whatever their desired priority level), without the need to store everything to a file. I assume this is a separate program with some form of hook into the service, but I'm unsure of where to start.

This is my first real windows service, so I seem to be missing some keywords for finding out how to do this... Are there any code samples, tutorials, references, etc. for how to do something like this?

UPDATE
A lot of helpful answers, I love it when there's many ways to solve a problem! I think I'm going to implement a self-hosting WCF based solution. I'm still very light on the details as I'm trying to learn about WCF (I believe it will prove quite useful for me in other projects)... but thus far, I've found the videos here to be the most helpful as an intro how-to.

Bale answered 17/11, 2010 at 18:24 Comment(0)
C
8

What you can do is have the windows service have way of registering for an event (you can do this through using Windows Communication Foundation). When your error comes up, it fires that event, and your winforms app will be notified. It's called a duplex contract:

http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/0eb69998-0388-4731-913e-fb205528d374/

http://msdn.microsoft.com/en-us/library/ms731184.aspx

Actually the really cool thing is that you can have multiple applications listening this way too. So you can display it on screen, and have another application log it etc. without the two external apps knowing anything about each other.

Caffrey answered 17/11, 2010 at 18:28 Comment(1)
Good answer, for using a platform-standard mechanism and for providing comprehensive documentation references.Pindling
T
3

I know this has already been mentioned, but use Windows Communication Foundation (WCF). Specifically, use the Publish-Subscribe Framework developed by Juval Lowy, author of Programming WCF Services. The details are described in this excellent MSDN article, and the source code is available for free at Lowy's website.

The neat thing about this framework is that it decouples the publisher, e.g., your Windows service, from any subscribers, e.g., your GUI. The publisher "publishes" events that are of interest to the Pub/Sub Service, which is always available. From the publisher's point of view, it doesn't matter if there are any subscribers or not. The Pub/Sub Service takes care of routing events to any and all registered subscribers. In this way, your Windows service publishes events as they occur, your GUI will subscribe/unsubscribe to the Pub/Sub Service when it loads/exits, and the Pub/Sub Service will notify your GUI as events occur.

I have used this setup in my project, and it works extremely well.

Tallith answered 18/11, 2010 at 3:36 Comment(0)
S
2

alt text

I've actually used the BitFactory Logger that has a socket logger that you can use for this purpose.

Shamus answered 17/11, 2010 at 18:29 Comment(0)
S
2

What you're describing is inter-process communication, which can get messy.

The easiest and most elegant, but probably least reactive, is to have the service write entries as small text files (or append to a log), and have your GUI use a FileSystemWatcher to detect new files or updates to the log file, and read the file. You have to ensure that the service opens the file for appending in a "shared" manner, allowing read-only access while it's writing. Otherwise, you'll block one process or the other, probably causing lost messages.

Processes can communicate through some built-in pipelines. if your service writes messages to its StandardOutput pipe, the GUI can remotely attach a listener and receive events when messages are written. This is probably the most elegant non-file way to do what you want. Research the Process class, especially the OutputDataReceived event. You'll have to go look for the process from your GUI by some uniquely identifying information, using GetProcess().

Spinule answered 17/11, 2010 at 18:34 Comment(3)
Personally I like pipes, but that's probably because I'm most familier with them. You can also use a named pipe rather than a standard pipe.Ewing
The file method was actually one idea I'd though of, but couldn't find a clean way of doing it. The FileSystemWatcher looks like a very good solution for that! I don't think Pipes provide a clean solution for me... If I understand correctly, the service will need to know about the GUI (to redirect it's StandardOutput) at startup, which I'm not sure it will... Is this correct, or am I missing something?Bale
Not if you use named pipes. You create a pipe with a globally unique name (usually something like "\\.\pipe\Company Name\Application Name\Pipe Name"). The service creates the pipe when it starts and the GUI app can connect to that pipe by name.Ewing
S
1

I've found that a Named Pipe communication with a System Tray application was the simplest way to display notifications from a Windows Service. This is because in Windows 10 services run with different permissions than the logged in user, so the notification app needs to perform IPC with the service.

Here you could put this into the server:

using System;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleServerApp
{
    class Program
    {
        static void Main(string[] args)
        {
            StartServer();
            Task.Delay(1000).Wait();
        }

        static void StartServer()
        {
            Task.Factory.StartNew(() =>
            {
                var server = new NamedPipeServerStream("PipesOfPiece");
                server.WaitForConnection();
                StreamReader reader = new StreamReader(server);
                StreamWriter writer = new StreamWriter(server);
                while (true)
                {
                    var line = reader.ReadLine();
                    writer.WriteLine(String.Join("", line.Reverse()));
                    writer.Flush();
                }
            });
        }
    }
}

Then put this into your client:

using System;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //Client
            var client = new NamedPipeClientStream("PipesOfPiece");
            client.Connect();
            StreamReader reader = new StreamReader(client);
            StreamWriter writer = new StreamWriter(client);

            while (true)
            {
                string input = Console.ReadLine();
                if (String.IsNullOrEmpty(input)) break;
                writer.WriteLine(input);
                writer.Flush();
                Console.WriteLine(reader.ReadLine());
            }
        }
    }
}

Then change your ConsoleServerApp to a Winforms application so that it can display the notification whenever the windows service sends it a message:

    public Form1()
    {
        InitializeComponent();

        StartServer();
        Task.Delay(_threadJoinTimeout).Wait();

    }

    public void DisplayMessage()
    {
        this.notifyIcon1.BalloonTipIcon = ToolTipIcon.Info;
        this.notifyIcon1.BalloonTipText = "Welcomd!";
        this.notifyIcon1.BalloonTipTitle = "Title";
        this.notifyIcon1.ShowBalloonTip(2000);
    }

    void StartServer()
    {
        Task.Factory.StartNew(() =>
        {
            var server = new NamedPipeServerStream("PipesOfPiece");
            server.WaitForConnection();
            StreamReader reader = new StreamReader(server);

            while (true)
            {
                var line = reader.ReadLine();
                DisplayMessage();
            }
        });
    }

Then put the ConsoleClientApp into your Windows Service.

For details on the pipe please see Example of Named Pipes For the System Tray application please see http://www.tutorialspanel.com/create-system-tray-icon-windows-forms-application-using-c-vb-net/#:~:text=Below%20is%20an%20example%20of%20how%20to%20create,Step%203.%20Add%20an%20icon%20to%20the%20NotifyIcon Here are tips on using the TopShelf NuGet package which allows you to debug your Windows Service as a Console Application: https://www.codeproject.com/Articles/881511/SignalR-with-Self-hosted-Windows-Service

Spirometer answered 15/7, 2020 at 19:14 Comment(0)
M
0

You need to look for "synchronization" and "inter-process communication". In your case the service would use the global event or semaphore to signal presence of data, and GUI process would check event/semaphore state and read the updates from event log or from file.

There exist more complicated scenarios, but the above is a good starting point.

Michaella answered 17/11, 2010 at 18:28 Comment(0)
C
0

Observer pattern!

Perhaps a delegate for all observable models that you can hook into with your service?

Chiropteran answered 17/11, 2010 at 18:29 Comment(0)
T
0

.NET remoting over IPC channel.

Tompion answered 17/11, 2010 at 18:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.