Search code examples
c#wpfdebuggingsignalrpoco

How to debug SignalR in order to find what prevent an instance of a class from being transfered from the server to a C# client (WPF here)


I'm using Signal to communicate between an ASP.NET server and a WPF client.

According to my reading of the documentation, we should use simple POCO class using SignalR. But the documentation sample also use an enum. My class does reference another POCO class. In the documentation sample I did not find any other class reference. Also my class is not fully POCO.

Actually the class I try to transfer is pretty much a POCO (but not 100%) and it is not transfered properly into SignalR. In the client following code, the lambda is never called either if the server send a "LogItem" to the client: HubConnection.On<LogItem>("NewLogItem", (logItem)=> OnNewLogItemFromServer(logItem));. Server and client share the exact same library containing the "LogItem" class.

But I can't find what prevent SignalR from accepting my class instance transfer. I tried many things without success in order to find the offending property in my class "LogItem" (class instance being transfered) which prevent the transfer using SignalR.

I try that: Logging and diagnostics in ASP.NET Core SignalR but it didn't help me to find the offending (field/fields) of my LogItem class preventing it to be transfered.

Does anyone can tell me a way to debug SignalR in order to find the offending property or properties of a Class that prevent SignalR to transfer an instance of that Class?

My Code: I put my code in reference BUT I'm looking for a way to debug SignalR in general. Not just finding the exact actual cause because I will have to use SignalR for other things.

Note: I do not want to decode the JSON myself if SignalR framework is suppose to do it for me. I would like to use it like it is designed to be used.

That following code is called into my client when the server send me a "LogItem" but it is a "JsonElement" in it:

HubConnection.On<Object>("NewLogItem", (obj) =>
            { ...

This is my LogItem class Code as reference (but I'm looking for a general way to find problems that prevent class to be tfr in SignalR):

using CommunityToolkit.Mvvm.ComponentModel;
using General.Diagnostics;
using Microsoft.Extensions.Logging;
using System;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using System.Xml.Serialization;

namespace General.LogEx
{
    [DataContract(Namespace = "")]
    public class LogItem : ObservableObject
    {
        private const string DateFormat = "yyyy-MM-dd HH:mm:ss.fff";

        // ******************************************************************
        [DataMember]
        public UInt64 Id { get; set; }

        //private bool _isAcknowledged = false;
        //public bool IsAcknowledged
        //{
        //    get => _isAcknowledged;
        //    set => SetProperty(ref _isAcknowledged, value);
        //}

        // ******************************************************************
        public DateTime DateTime { get; set; }

        // ******************************************************************
        [XmlIgnore]
        [JsonIgnore]
        public string TimeStamp
        {
            get
            {
                return DateTime.ToString(DateFormat);
            }
            set
            {
                DateTime = DateTime.ParseExact(value, DateFormat, CultureInfo.InvariantCulture);
            }
        }

        // ******************************************************************
        [DataMember]
        public LogCategory LogCategory { get; set; }

        // ******************************************************************
        [DataMember]
        public LogLevel LogLevel { get; set; }

        // ******************************************************************
        private string _message = null;

        [DataMember]
        public string Message
        {
            get
            {
                return _message;
            }
            set
            {
                _message = value;
            }
        }

        // ******************************************************************
        private Exception _exception = null;

        [DataMember]
        public Exception Exception
        {
            get
            {
                return _exception;
            }
            set
            {
                _exception = value;
            }
        }

        // ******************************************************************
        [XmlIgnore]
        [JsonIgnore]
        public string ExceptionMessage
        {
            get
            {
                return _exception?.Message;
            }
        }

        [DataMember]
        public string MoreInfo { get; set; }
        // ******************************************************************

        [DataMember]
        public int Occurence { get; set; } = 1;

        /// <summary>
        /// Public only for serialization (XML or JSON)
        /// </summary>
        public LogItem()
        {
        }

        // ******************************************************************
        public LogItem(UInt64 id, LogCategory logCategory, LogLevel logLevel, string message)
        {
            Id = Id;
            DateTime = DateTime.Now;
            LogCategory = logCategory;
            LogLevel = logLevel;
            Message = FixMessage(message);
        }

        // ******************************************************************
        /// <summary>
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        private string FixMessage(string message)
        {
            //if (message != null && message.Length > 0)
            //{
            //    if (message[0] == ' ' || message[message.Length - 1] == ' ')
            //    {
            //        message = message.Trim();
            //    }

            //    if (message.EndsWith("."))
            //    {
            //        return message.Substring(0, message.Length - 1);
            //    }
            //}

            return message;
        }

        // ******************************************************************
        public LogItem(UInt64 id, LogCategory logCategory, LogLevel logType, string message, Exception exception)
        {
            Id = id;
            DateTime = DateTime.Now;
            LogCategory = logCategory;
            LogLevel = logType;
            Message = FixMessage(message);
            if (exception != null)
            {
                Exception = exception;

                MoreInfo = "Exception:\nMessage:\n" + exception.Message +
                    "\nSource:\n" + exception.Source +
                    "\nStack:\n" + exception.StackTrace +
                    "\nToString():\n" + exception.ToString();
            }
            else
            {
                if (LogLevel == LogLevel.Error || LogLevel == LogLevel.Critical)
                {
                    MoreInfo = StackUtil.GetStackTraceWithoutIntialTypes(new Type[] { typeof(Log), typeof(LogItem) }).ToString();

                    if (MoreInfo.EndsWith("\r\n"))
                    {
                        MoreInfo = MoreInfo.Remove(MoreInfo.Length - 2);
                    }
                }
            }
        }

        // ******************************************************************
        [XmlIgnore]
        [JsonIgnore]
        public string MessageAndException
        {
            get { return Message + ExceptionMessage; }
        }

        // ******************************************************************
        [XmlIgnore]
        [JsonIgnore]
        public string DateTimeFormated
        {
            get { return DateTime.ToString("yyyy-MM-dd hh:mm:ss.ffffff"); }
        }

        // ******************************************************************
        public void WriteTo(StreamWriter streamWriter)
        {
            streamWriter.Write(ToString());
        }

        // ******************************************************************
        [XmlIgnore]
        [JsonIgnore]
        public string QuickDescription
        {
            get
            {
                return string.Format($"{Id}, {DateTimeFormated}, {LogLevel}, {LogCategory} , {Message.Replace("\r\n", "[CRLF]")}");
            }
        }

        // ******************************************************************
        public override string ToString()
        {
            if (string.IsNullOrEmpty(MoreInfo))
            {
                if (Occurence == 1)
                {
                    // return String.Format($"{DateTimeFormated,-26}, {LogLevel,-12}, {Message}.");
                    return string.Format("{0,5}, {1,-26}, {2,-12}, {3}.", Id, DateTimeFormated, LogLevel, Message);
                }

                // return String.Format($"{DateTimeFormated,-26}, {LogLevel,-12}, {Message}. Occurence: {Occurence}.");
                return string.Format("{0,5}, {1,-26}, {2,-12}, {3}. Occurence: {4}.", Id, DateTimeFormated, LogLevel, Message, Occurence);
            }
            else
            {
                if (Occurence == 1)
                {
                    // return String.Format($"{DateTimeFormated,-26}, {LogLevel,-12}, {Message}. MoreInfo: {MoreInfo}.");
                    return string.Format("{0,5}, {1,-26}, {2,-12}, {3}. MoreInfo: {4}.", Id, DateTimeFormated, LogLevel, Message, MoreInfo);
                }

                // return String.Format($"{DateTimeFormated,-26}, {LogLevel,-12}, {Message}. Occurence: {Occurence}, MoreInfo: {MoreInfo}.");
                return string.Format("{0,5}, {1,-26}, {2,-12}, {3}. Occurence: {4}. MoreInfo: {5}.", Id, DateTimeFormated, LogLevel, Message, Occurence, MoreInfo);
            }
        }

        // ******************************************************************
    }
}

Solution

  • I finally found a solution to my specific problem and mainly a way to identify the source of it.

    So I was not able to receive SignalR specific object type (here: "LogItem"). So I replace the following function

    HubConnection.On<LogItem>("NewLogItem", (logItem)=> OnNewLogItemFromServer(logItem));
    

    With:

    HubConnection.On<Object>("NewLogItem", (obj) => {...
    

    But obj was in fact a "JsonElement" that needs to be deserialized to my specific type so I program it and it leads to an Exception showing the exact reason why the initial line did not work on the HubConnection.

    This is the code:

        HubConnection.On<Object>("NewLogItem", (obj) =>
        {
            if (obj is JsonElement jsonElement)
            {
                JsonSerializerOptions opts = new JsonSerializerOptions();
                opts.PropertyNameCaseInsensitive = true;
                var logItem2 = JsonSerializer.Deserialize<LogItem>(jsonElement, opts);
            }
        });
    

    And the exception I got:

    The exception

    The exception lead to solve my problem easily. My solution was to change the property "LogCategory" to be a class to a string (it was possible in my case). The contructor of LogCategory was complex and problematic. After I applied the solution, the Json deserialization went without any exception. After I correct my problem, I was able to revert back to my initial line were SigalR do all the deserialization for me.