ソースを参照

enable sending general data by attributes

Dalibor Votruba 2 年 前
コミット
9306ba1ca4

+ 1 - 0
Workbench/QMonitor/qmonlib/Attributes/MonitoredClassAttribute.cs

@@ -5,5 +5,6 @@
     {
         public string Caption { get; set; }
         public string Description { get; set; }
+        public MonItemGeneral.MonViewType ViewType { get; set; }
     }
 }

+ 7 - 0
Workbench/QMonitor/qmonlib/Attributes/MonitoredPropertyKeyAttribute.cs

@@ -0,0 +1,7 @@
+namespace qmonlib.Attributes
+{
+    public class MonitoredPropertyKeyAttribute : Attribute
+    {
+        public int KeyOrder { get; set; }
+    }
+}

+ 173 - 0
Workbench/QMonitor/qmonlib/Dependencies/Json/Binder.cs

@@ -0,0 +1,173 @@
+using System;
+using System.Diagnostics;
+using System.IO.Abstractions;
+using System.Linq;
+using System.Reflection;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Quadarax.Foundation.Core.Json
+{
+    public class Binder
+    {
+        private IFileSystem _fileSystem;
+
+        public JsonSerializerOptions SerializerOptions {get;private set;}
+
+        public Binder(JsonSerializerOptions options): this(new FileSystem(), options)
+        {
+        }
+        public Binder(IFileSystem fileSystemAbstraction, JsonSerializerOptions options)
+        {
+            _fileSystem = fileSystemAbstraction ?? throw new ArgumentNullException(nameof(fileSystemAbstraction));
+            SerializerOptions = options;
+        }
+
+        public Binder(IFileSystem fileSystemAbstraction, bool isPrettyPrint = true, bool isIgnoringNullValues = true, int maxDepth = 100)
+        {
+            _fileSystem = fileSystemAbstraction ?? throw new ArgumentNullException(nameof(fileSystemAbstraction));
+            var jsonOpt = new JsonSerializerOptions
+            {
+                IgnoreNullValues = isIgnoringNullValues,
+                WriteIndented = isPrettyPrint,
+                PropertyNameCaseInsensitive = true,
+                PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+                MaxDepth = maxDepth
+            };
+            jsonOpt.Converters.Add(new JsonStringEnumConverter());
+            SerializerOptions = jsonOpt;
+        }
+
+        public TObject Load<TObject>(string fileName)
+        {
+            return (TObject) Load(fileName, typeof(TObject));
+        }
+        public object Load(string fileName, Type targetType)
+        {
+            if (targetType == null)
+                throw new ArgumentNullException(nameof(targetType));
+
+            if (string.IsNullOrEmpty(fileName))
+                throw new ArgumentNullException(nameof(fileName));
+
+            using (var reader = _fileSystem.File.OpenText(fileName))
+            {
+                var result = JsonSerializer.Deserialize(reader.ReadToEnd(),targetType, SerializerOptions);
+                return result;
+            }
+        }
+
+        public object LoadFromString(string jsonString, Type targetType)
+        {
+            if (targetType == null)
+                throw new ArgumentNullException(nameof(targetType));
+
+            if (string.IsNullOrEmpty(jsonString))
+                throw new ArgumentNullException(nameof(jsonString));
+
+            
+            var result = JsonSerializer.Deserialize(jsonString,targetType, SerializerOptions);
+            return result;
+        }
+
+        public void LoadTo(string fileName, object target, Type targetType)
+        {
+            if (targetType == null)
+                throw new ArgumentNullException(nameof(targetType));
+     
+            if (target == null)
+                throw new ArgumentNullException(nameof(target));
+            var shadow = Load(fileName, targetType);
+            
+            foreach (var property in shadow.GetType().GetProperties())
+            {
+                if (target.GetType().GetProperties().Any(x => x.Name == property.Name && property.GetValue(shadow) != null))
+                {
+                    if (property.GetType().IsClass && property.PropertyType.Assembly.FullName == targetType.Assembly.FullName)
+                    {
+                        var mapMethod = typeof(Binder).GetMethod("LoadTo");
+                        var genericMethod = mapMethod.MakeGenericMethod(property.GetValue(shadow).GetType());
+                        var obj2 = genericMethod.Invoke(null, new object[] { property.GetValue(shadow), JsonSerializer.Serialize(property.GetValue(shadow)) });
+
+                        foreach (var property2 in obj2.GetType().GetProperties())
+                        {
+                            if (property2.GetValue(obj2) != null)
+                            {
+                                property.GetValue(target).GetType().GetProperty(property2.Name).SetValue(property.GetValue(target), property2.GetValue(obj2));
+                            }
+                        }
+                    }
+                    else if (property.CanWrite)
+                    {
+                        property.SetValue(target, property.GetValue(shadow));
+                    }
+                }
+            }
+
+        }
+
+        public void LoadFromStringTo(string jsonString, object target, Type targetType)
+        {
+            if (targetType == null)
+                throw new ArgumentNullException(nameof(targetType));
+     
+            if (target == null)
+                throw new ArgumentNullException(nameof(target));
+            var shadow = LoadFromString(jsonString, targetType);
+            
+            foreach (var property in shadow.GetType().GetProperties())
+            {
+                if (target.GetType().GetProperties().Any(x => x.Name == property.Name && property.GetValue(shadow) != null))
+                {
+                    if (property.GetType().IsClass && property.PropertyType.Assembly.FullName == targetType.Assembly.FullName)
+                    {
+                        var mapMethod = typeof(Binder).GetMethod("LoadFromStringTo");
+                        var genericMethod = mapMethod.MakeGenericMethod(property.GetValue(shadow).GetType());
+                        var obj2 = genericMethod.Invoke(null, new object[] { property.GetValue(shadow), JsonSerializer.Serialize(property.GetValue(shadow)) });
+
+                        foreach (var property2 in obj2.GetType().GetProperties())
+                        {
+                            if (property2.GetValue(obj2) != null)
+                            {
+                                property.GetValue(target).GetType().GetProperty(property2.Name).SetValue(property.GetValue(target), property2.GetValue(obj2));
+                            }
+                        }
+                    }
+                    else if (property.CanWrite)
+                    {
+                        property.SetValue(target, property.GetValue(shadow));
+                    }
+                }
+            }
+
+        }
+
+        public void LoadTo<TObject>(string fileName, TObject target)
+        {
+            LoadTo(fileName, target, typeof(TObject));
+        }
+
+        public void Save<TObject>(string fileName, TObject bindingObject)
+        {
+            if (string.IsNullOrEmpty(fileName))
+                throw new ArgumentNullException(nameof(fileName));
+            if (bindingObject==null)
+                throw new ArgumentNullException(nameof(bindingObject));
+
+            var configuration = JsonSerializer.SerializeToUtf8Bytes(bindingObject,SerializerOptions);
+            using (var writer = _fileSystem.File.Create(fileName))
+            {
+                writer.Write(configuration);
+                writer.Flush();
+            }
+        }
+
+        public string SaveToString<TObject>(TObject bindingObject)
+        {
+            if (bindingObject==null)
+                throw new ArgumentNullException(nameof(bindingObject));
+
+            return JsonSerializer.Serialize(bindingObject,bindingObject.GetType());
+        }
+    }
+}

+ 35 - 0
Workbench/QMonitor/qmonlib/Dependencies/Json/IListConverterFactory.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Quadarax.Foundation.Core.Json
+{
+    public class IListInterfaceConverterFactory : JsonConverterFactory
+    {
+        public IListInterfaceConverterFactory(Type interfaceType)
+        {
+            this.InterfaceType = interfaceType;
+        }
+
+        public Type InterfaceType { get; }
+
+        public override bool CanConvert(Type typeToConvert)
+        {
+            if (typeToConvert == typeof(IList<>).MakeGenericType(this.InterfaceType)
+                && typeToConvert.GenericTypeArguments[0] == this.InterfaceType)
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+        {
+            return (JsonConverter)Activator.CreateInstance(
+                typeof(ListConverter<>).MakeGenericType(this.InterfaceType));
+        }
+    }
+
+}

+ 18 - 0
Workbench/QMonitor/qmonlib/Dependencies/Json/InterfaceConverter.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Quadarax.Foundation.Core.Json
+{
+    public class InterfaceConverter<M, I> : JsonConverter<I> where M : class, I
+    {
+        public override I Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        {
+            return JsonSerializer.Deserialize<M>(ref reader, options);
+        }
+
+        public override void Write(Utf8JsonWriter writer, I value, JsonSerializerOptions options)
+        {
+        }
+    }
+}

+ 30 - 0
Workbench/QMonitor/qmonlib/Dependencies/Json/InterfaceConverterFactory.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Quadarax.Foundation.Core.Json
+{
+    public class InterfaceConverterFactory : JsonConverterFactory
+    {
+        public InterfaceConverterFactory(Type concrete, Type interfaceType)
+        {
+            this.ConcreteType = concrete;
+            this.InterfaceType = interfaceType;
+        }
+
+        public Type ConcreteType { get; }
+        public Type InterfaceType { get; }
+
+        public override bool CanConvert(Type typeToConvert)
+        {
+            return typeToConvert == this.InterfaceType;
+        }
+
+        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+        {
+            var converterType = typeof(InterfaceConverter<,>).MakeGenericType(this.ConcreteType, this.InterfaceType);
+
+            return (JsonConverter)Activator.CreateInstance(converterType);
+        }
+    }
+}

+ 25 - 0
Workbench/QMonitor/qmonlib/Dependencies/Json/ListConverter.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Quadarax.Foundation.Core.Json
+{
+    public class ListConverter<M> : JsonConverter<IList<M>>
+    {
+        public override IList<M> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        {
+            return JsonSerializer.Deserialize<List<M>>(ref reader, options);
+        }
+
+        public override bool CanConvert(Type typeToConvert)
+        {
+            return base.CanConvert(typeToConvert);
+        }
+
+        public override void Write(Utf8JsonWriter writer, IList<M> value, JsonSerializerOptions options)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 41 - 0
Workbench/QMonitor/qmonlib/Dependencies/Logging/ConsoleLog.cs

@@ -0,0 +1,41 @@
+namespace Quadarax.Foundation.Core.Logging
+{
+    public class ConsoleLog : ILog
+    {
+        private string _typeName;
+
+        public ConsoleLog(Type owningType)
+        {
+            _typeName = owningType.Name;
+        }
+
+        public ConsoleLog(string owningTypeName)
+        {
+            _typeName = owningTypeName;
+        }
+
+        public void Log(LogSeverityEnum severity, int code, string message)
+        {
+			Log(severity, $"Code:{code}, {message}");            
+        }
+
+        public void Log(LogSeverityEnum severity, string message)
+        {
+            var now = DateTime.Now;
+            var thNum = Thread.CurrentThread.ManagedThreadId;
+
+            var final = $"[{severity}:{now.ToLongTimeString()}:{thNum}]#{_typeName}: {message}";
+            Console.WriteLine(final);
+        }
+
+        public void Log(LogSeverityEnum severity, string message, Exception? exception)
+        {
+            Log(severity, $"{message}\n{exception}");
+        }
+
+        public void Log(LogSeverityEnum severity, int code, string message, Exception? exception)
+        {
+            Log(severity, string.Format("{2} - {0}\n{1}", message,exception, code));
+        }
+    }
+}

+ 16 - 0
Workbench/QMonitor/qmonlib/Dependencies/Logging/ConsoleLogger.cs

@@ -0,0 +1,16 @@
+
+namespace Quadarax.Foundation.Core.Logging
+{
+    public class ConsoleLogger : ILogger
+    {
+        public ILog GetLogger(string loggerName)
+        {
+            return new ConsoleLog(loggerName);
+        }
+
+        public ILog GetLogger(Type loggerForType)
+        {
+            return new ConsoleLog(loggerForType);
+        }
+    }
+}

+ 12 - 0
Workbench/QMonitor/qmonlib/Dependencies/Logging/ILog.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace Quadarax.Foundation.Core.Logging
+{
+    public interface ILog
+    {
+        void Log(LogSeverityEnum severity, int code, string message);
+        void Log(LogSeverityEnum severity, string message);
+        void Log(LogSeverityEnum severity, string message, Exception? exception);
+        void Log(LogSeverityEnum severity, int code, string message, Exception? exception);
+    }
+}

+ 9 - 0
Workbench/QMonitor/qmonlib/Dependencies/Logging/ILogHandler.cs

@@ -0,0 +1,9 @@
+using System;
+
+namespace Quadarax.Foundation.Core.Logging
+{
+    public interface ILogHandler
+    {
+        void Log(LogSeverityEnum type, string message, Exception? e = null);
+    }
+}

+ 10 - 0
Workbench/QMonitor/qmonlib/Dependencies/Logging/ILogger.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace Quadarax.Foundation.Core.Logging
+{
+    public interface ILogger
+    {
+        ILog GetLogger(string loggerName);
+        ILog GetLogger(Type loggerForType);
+    }
+}

+ 12 - 0
Workbench/QMonitor/qmonlib/Dependencies/Logging/LogSeverityEnum.cs

@@ -0,0 +1,12 @@
+namespace Quadarax.Foundation.Core.Logging
+{
+    public enum LogSeverityEnum
+    {
+        Trace,
+        Debug,
+        Info,
+        Warn,
+        Error,
+        Fatal
+    }
+}

+ 9 - 0
Workbench/QMonitor/qmonlib/MonGeneral.cs

@@ -0,0 +1,9 @@
+namespace qmonlib
+{
+    [Serializable]
+    public class MonGeneral
+    {
+        public string InstanceIdentifier { get; set; }
+        public List<MonItemGeneral> Items { get; set; } = new List<MonItemGeneral>();
+    }
+}

+ 11 - 1
Workbench/QMonitor/qmonlib/MonItemGeneral.cs

@@ -1,23 +1,33 @@
 using System.Reflection;
+using System.Text.Json.Serialization;
 
 namespace qmonlib
 {
     [Serializable]
     public class MonItemGeneral
     {
-        public string InstanceIdentifier { get; set; }
+        [JsonPropertyName("cpt")]
         public string Caption { get; set; }
+        [JsonPropertyName("dsc")]
         public string Description { get; set; }
+        [JsonPropertyName("vty")]
         public MonViewType ViewType { get; set; }
+        [JsonPropertyName("key")]
+        public List<int> KeyOrdinals { get; set; } = new List<int>();
 
         public List<ItemDescriptor> Items { get; set; } = new List<ItemDescriptor>();
 
         public class ItemDescriptor
         {
+            [JsonPropertyName("nme")]
             public string Name { get; set; }
+            [JsonPropertyName("tnm")]
             public string TypeName { get; set; }
+            [JsonPropertyName("lbl")]
             public string Label { get; set; }
+            [JsonPropertyName("dsc")]
             public string Description { get; set; }
+            [JsonPropertyName("ord")]
             public int Order { get; set; }
 
             [NonSerialized] public PropertyInfo? _propertyInfo;

+ 11 - 0
Workbench/QMonitor/qmonlib/Program.cs

@@ -1,2 +1,13 @@
 // See https://aka.ms/new-console-template for more information
+
+using qmonlib;
+using Quadarax.Foundation.Core.Logging;
+
 Console.WriteLine("Hello, World!");
+
+var logger = new ConsoleLogger();
+using (var cli = new QMonClient("TST", QMonConfiuration.CreateDefault(), logger))
+{
+    Console.WriteLine("Press any key to exit...");
+    Console.ReadKey();
+}

+ 54 - 6
Workbench/QMonitor/qmonlib/QMonClient.cs

@@ -1,7 +1,11 @@
-using System.Net;
+using System.ComponentModel.DataAnnotations;
+using System.IO.Abstractions;
+using System.Net;
 using System.Net.Sockets;
 using System.Reflection;
+using System.Text;
 using qmonlib.Attributes;
+using Quadarax.Foundation.Core.Logging;
 using Quadarax.Foundation.Core.Object;
 using Quadarax.Foundation.Core.Reflection.Extensions;
 
@@ -31,11 +35,15 @@ namespace qmonlib
         private Timer? _timerData;
 
         private IDictionary<string, MonItemGeneral> _generalCache = new Dictionary<string, MonItemGeneral>();
+        private ILog? _log;
+        private Quadarax.Foundation.Core.Json.Binder _binder = new Quadarax.Foundation.Core.Json.Binder(new FileSystem(),false,true,100);
+        
         #endregion
 
         #region *** Constructors ***
-        public   QMonClient(string instanceIdentifier, QMonConfiuration configuration)     
+        public QMonClient(string instanceIdentifier, QMonConfiuration configuration, ILogger? externalLogger)
         {
+            _log = externalLogger?.GetLogger(GetType());
             InstanceIdentifier = instanceIdentifier ?? throw new ArgumentNullException(nameof(instanceIdentifier));
             _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
             SetEnabled(_configuration.Enabled);
@@ -52,13 +60,14 @@ namespace qmonlib
                 {
                     var monItemGeneral = new MonItemGeneral
                     {
-                        InstanceIdentifier = InstanceIdentifier,
                         Caption = type.GetCustomAttribute<MonitoredClassAttribute>()?.Caption ?? type.Name,
                         Description = type.GetCustomAttribute<MonitoredClassAttribute>()?.Description ?? string.Empty,
                         ViewType = MonItemGeneral.MonViewType.Item
                     };
 
                     var properties = type.GetPropertiesWithAttribute<MonitoredPropertyAttribute>(true);
+                    var keys = new List<Tuple<string,int>>();
+
                     foreach (var property in properties)
                     {
                         var itemDescriptor = new MonItemGeneral.ItemDescriptor
@@ -71,6 +80,17 @@ namespace qmonlib
                             _propertyInfo = property
                         };
                         monItemGeneral.Items.Add(itemDescriptor);
+                        if (property.GetCustomAttribute<MonitoredPropertyKeyAttribute>() != null)
+                        {
+                            keys.Add(new Tuple<string, int>(property.Name, property.GetCustomAttribute<MonitoredPropertyKeyAttribute>()!.KeyOrder));
+                        }
+                    }
+
+                    foreach (var key in keys.OrderBy(x => x.Item2))
+                    {
+                        var ord = monItemGeneral.Items.FindIndex(x => x.Name == key.Item1);
+                        if (ord>=0)
+                            monItemGeneral.KeyOrdinals.Add(ord);
                     }
                     _generalCache.Add(type.FullName!, monItemGeneral);
                 }
@@ -103,12 +123,38 @@ namespace qmonlib
 
         private void OnTimerData(object? state)
         {
-            throw new NotImplementedException();
+            
         }
 
         private void OnTimerGeneral(object? state)
         {
-            throw new NotImplementedException();
+            if (state == null) throw new ArgumentNullException(nameof(state));
+            var owner = (QMonClient)state;
+            if (owner._udpClientGeneral == null) throw new InvalidOperationException("UDP client channel General not open.");
+            try
+            {
+              
+                var data = new MonGeneral()
+                {
+                    InstanceIdentifier = owner.InstanceIdentifier,
+                    Items = new List<MonItemGeneral>()
+                };
+                data.Items.AddRange(owner._generalCache.Values);
+                var dataOut = Serialize(data);
+                _udpClientGeneral?.Send(dataOut);
+                _log?.Log(LogSeverityEnum.Trace, $"[{InstanceIdentifier}] Sent General packet size = {dataOut.Length} bytes");
+            }
+            catch(Exception ex)
+            {
+                _log?.Log(LogSeverityEnum.Error, $"[{InstanceIdentifier}] Error sending general data", ex);
+            }
+        }
+
+        private byte[] Serialize(MonGeneral data)
+        {
+            if (data == null) throw new ArgumentNullException(nameof(data));
+            var json = _binder.SaveToString(data);
+            return Encoding.UTF8.GetBytes(json);
         }
 
         private void Close()
@@ -134,7 +180,9 @@ namespace qmonlib
 
         private UdpClient CreateUdpClient(Uri targetUri)
         {
-            var udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(targetUri.DnsSafeHost), targetUri.Port));
+            var ep = new IPEndPoint(IPAddress.Parse(targetUri.DnsSafeHost), targetUri.Port);
+            var udpClient = new UdpClient();
+            udpClient.Connect(ep);
             return udpClient;
         }
         private void SetEnabled(bool value)

+ 2 - 2
Workbench/QMonitor/qmonlib/QMonConfiuration.cs

@@ -14,8 +14,8 @@
             return new QMonConfiuration
             {
                 Enabled = true,
-                TargetUriGeneral = new Uri("udp://0.0.0.0:5100"),
-                TargetUriData = new Uri("udp://0.0.0.0:5101"),
+                TargetUriGeneral = new Uri("udp://127.0.0.1:5100"),
+                TargetUriData = new Uri("udp://127.0.0.1:5101"),
                 EmitGeneralInterval = TimeSpan.FromSeconds(5),
                 EmitDataInterval = TimeSpan.FromSeconds(1)
             };

+ 17 - 0
Workbench/QMonitor/qmonlib/Test/TestItem.cs

@@ -0,0 +1,17 @@
+using qmonlib.Attributes;
+
+namespace qmonlib.Test
+{
+    [MonitoredClass(Caption = "Person", Description = "Simple personal info", ViewType = MonItemGeneral.MonViewType.List)]
+    public class TestItem
+    {
+        [MonitoredPropertyKey(KeyOrder = 1)]
+        [MonitoredProperty(Name = "FirstName", Label="First Name", Description = "First name of the person", Order = 1)]
+        public string FirstName { get; set; }
+        [MonitoredPropertyKey(KeyOrder = 0)]
+        [MonitoredProperty(Name = "LastName", Label="Last Name", Description = "Last name of the person", Order = 0)]
+        public string LastName { get; set; }
+        [MonitoredProperty(Name = "Age", Description = "Age of the person", Order = 2)]
+        public int Age { get; set;}
+    }
+}

+ 5 - 1
Workbench/QMonitor/qmonlib/qmonlib.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
@@ -7,4 +7,8 @@
     <Nullable>enable</Nullable>
   </PropertyGroup>
 
+  <ItemGroup>
+    <PackageReference Include="System.IO.Abstractions" Version="19.2.69" />
+  </ItemGroup>
+
 </Project>