Pinger Sample
The Pinger sample demonstrates how to create a minimal monitoring / IoT application. It uses one additional (automation) backend service/agent to perform the business logic and capture the ping response results of the hosts/machines that are monitored. It also demonstrates the usage of different .NET GUI frameworks (Avalonia UI, MAUI, WPF, WinForms) for building applications. It also uses tha LiveCharts2 plotting library.
The user can add/edit hosts/devices and monitor its status. Also by clicking the device the user can select one device to view its ping history.
Download or clone the samples from GitHub samples repository.
Quick Start
-
Open SimplePinger.sln solution in Visual Studio
-
Build the solution
-
Run or Debug the PingerServer
-
Run or Debug the PingerAgent. The PingerAgent is a Client of the Objects Server and requires to authenticate. For simplicity we have hardcoded the credentials.
-
Run or Debug one instance of one of the Pinger GUI applications, eg PingerWinFormsApp
-
To login use one of the pre-configured users that are in the app.config of the server (those accounts are there only for initialization, they are stored in db). You can use the same user in more than one instances.
- After login you can add a Device. Click the Device->Add menu item. Then enter a friendly name, the host address and the pinging interval and click OK.
The pinger agent will get the update and start pinging the device added.
- Then add more hosts or open additional instances of the GUI application. All instances will be synchronized in real-time.
Pinger Codebase
The Pinger sample consists of the following projects:
- PingerDomain: The Domain (Entity Model) of the application.
- PingerServer: The Cognibase Object Server.
- PingerAgent: The backend service that performs the actual pinging.
- PingerUiCommon: A common project for the ViewModels of the MVVM GUI applications.
- PingerAvaloniaApp: The Desktop application built with Avalonia UI and CommunityToolkit.Mvvm.
- PingerMauiApp: The Desktop application built with MAUI and CommunityToolkit.Mvvm.
- PingerWinFormsApp: The Desktop application built with Windows Forms.
- PingerWpfApp: The Desktop application built with WPF and CommunityToolkit.Mvvm.
Pinger Domain
In the Pinger Domain project there are two folders:
- Entities: Where the entities reside.
- System: Where two classes exits that make this assembly be treated as Domain from Cognibase (see more here)
The Pinger Domain contains the following Entities. Note that For each device/host, we create one instance of the Device
class and once instance of the DevicePingResult
class. The second contains the actual result value, but the first has a wrapping property to be used easier in GUI applications and minimize conflict effects.
Device
Represents the device. This item contains the configuration (friendly name, host, interval) as well as it has a calculative property that is wraps the actual ping result.
[PersistedClass]
public class Device : DataItem
{
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }
[PersistedProperty]
public string Name { get => getter<string>(); set => setter(value); }
[PersistedProperty]
public string Host { get => getter<string>(); set => setter(value); }
[PersistedProperty(DefaultValue = 1)]
public int PingInterval { get => getter<int>(); set => setter(value); }
[PersistedProperty(ReverseRef = nameof(DevicePingResult.DeviceRef), AssociationType = AssociationType.CompositionChild)]
public DevicePingResult Result { get => getter<DevicePingResult>(); set => setter(value); }
[IndirectProperty(Target = nameof(Result))]
public int PingResult
{
get
{
int threshold = 2 * PingInterval;
if (threshold < 10)
threshold = 10;
if (DateTime.Now - TimeSpan.FromSeconds(threshold) > Result.LastPingTime)
return 0;
return Result.Value;
}
}
internal void raisePingResultChanges()
{
OnPropertyChanged(this, new PropertyChangedEventArgs(nameof(PingResult)));
}
}
Ping Result
Represents the current device ping result.
[PersistedClass]
public class DevicePingResult : DataItem
{
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }
[PersistedProperty(ReverseRef = nameof(Device.Result), AssociationType = AssociationType.CompositionParent)]
public Device DeviceRef { get => getter<Device>(); set => setter(value); }
[PersistedProperty]
public int Value { get => getter<int>(); set => setter(value); }
[PersistedProperty]
public DateTime LastPingTime { get => getter<DateTime>(); set => setter(value); }
// override on property change to fire property change on the Device class
protected override void OnPropertyChanged(object source, PropertyChangedEventArgs e)
{
base.OnPropertyChanged(source, e);
if (DeviceRef != null && (e.PropertyName == nameof(Value) || e.PropertyName == nameof(LastPingTime)))
DeviceRef.raisePingResultChanges();
}
}
Ping History Item
Represents the historic item, i.e. each and every one ping result captured for a device.
[PersistedClass]
public class PingHistoryItem : DataItem
{
[PersistedProperty(IdOrder = 1, AutoValue = AutoValue.Identity)]
public long Id { get => getter<long>(); set => setter(value); }
[PersistedProperty]
public long DeviceId { get => getter<long>(); set => setter(value); }
[PersistedProperty]
public int Value { get => getter<int>(); set => setter(value); }
[PersistedProperty]
public DateTime Time { get => getter<DateTime>(); set => setter(value); }
}
Pinger Server
The Pinger Server project uses the (default) SQLite adapter and references the Pinger domain. It also contains a few lines to bootstrap the Cognibase Object Server.
Below is the Program.cs
:
[MTAThread]
private static void Main(string[] args)
{
// enable Serilog
LogManager.RegisterAgentType<SerilogAgent>();
// start the object server
ServerManager.StartInConsole();
}
Also in the app.config
file there is the the definition of the Pinger Domain in the DataStore
element.
Pinger Agent
The pinger agent is the backend service (console application) that performs the actual pinging to the devices. The main logic is implemented in the class PingController
. The Ping Controllers continuously listens for new device configuration changes and then spawns an async task, in class DevicePingTask
, to perform the ping for each host. Then it writes the result to the DevicePingResult
instance of the device as well as creates a new instance of the PingHistoryItem
class for each new ping result. The Ping Controller is also responsible for deleting old history items based on a pre-configured duration (default is 1 month). Below is an example of the logic when the ping result returns.
private void onPing(Device device, PingReply reply, DateTime time)
{
// lock to prevent incomplete changes save
lock (_comLockObject)
{
// if success log and apply value
if (reply != null && reply.Status == IPStatus.Success)
{
Console.WriteLine($"{time} : Ping success for {device.Host}");
device.Result.Value = 2;
}
else if (reply != null) // if reply with error
{
Console.WriteLine($"ERROR: {time} : Ping error for host {device.Host}.");
device.Result.Value = 1;
}
else // if reply is null -> no output
{
device.Result.Value = 1;
}
// mark
device.Result.LastPingTime = time;
// create new history item and add it to list for saving
PingHistoryItem? historyItem = App.Client.CreateDataItem<PingHistoryItem>();
historyItem.DeviceId = device.Id;
historyItem.Time = device.Result.LastPingTime;
historyItem.Value = device.Result.Value;
newHistoryItemsToSave.Add(historyItem);
}
}
Pinger UI Common
This project contains the View Models for the XAMl based GUI apps. It uses the CommunityToolkit.Mvvm
package.
The MainViewModel
contains the code for setting up the LiveCharts2 chart as well as the logic for resetting the chart when the user selects a different machine to see the history.
The DeviceEditVm
contains the logic for adding hosts/devices and editing its properties.
Pinger Apps
The Pinger sample demonstrates the usage of different .NET GUI frameworks with Cognibase. You can explore the different frameworks by exploring the codebase.