Search code examples
c#jsonwpftcpclienttcplistener

How to read serialized and deserialized json object from TCPClient using WPF in C#?


I have a working code and its a window wpf application that start and stop the timer. What i am struggling now to create TCPClient from my code, that will read a serialized and deserialized json object using TCP Socket.

// Requirement is when button is pressed from the start button on my window application.

Pressing Start should start the echo process as described further below;

  • Repeat the below exactly every 500 ms
  • Create object that contains an initialized GUID
  • Serialize the object using a Json serializer
  • Send the json string using a TCP socket to the echotool
    (The echotool will echo back all received data)
  • Parse and assemble the echo'ed json data packet(s) and deserialize it back into an object
  • Add this echo result to the GUI Grid into the relevant columns
    (Started = timestamp when this echo process started , Processed = timestamp after deserialization into an object , Elapsed = milliseconds elapsed from Started to Processed).

// TCPClient i took from the microsoft documentation.

    static void Connect(String server, String message)
    {
      try
      {
        // Create a TcpClient.
        // Note, for this client to work you need to have a TcpServer
        // connected to the same address as specified by the server, port
        // combination.
        Int32 port = 13000;
        TcpClient client = new TcpClient(server, port);
    
        // Translate the passed message into ASCII and store it as a Byte array.
        Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);
    
        // Get a client stream for reading and writing.
       //  Stream stream = client.GetStream();
    
        NetworkStream stream = client.GetStream();
    
        // Send the message to the connected TcpServer.
        stream.Write(data, 0, data.Length);
    
        Console.WriteLine("Sent: {0}", message);
    
        // Receive the TcpServer.response.
    
        // Buffer to store the response bytes.
        data = new Byte[256];
    
        // String to store the response ASCII representation.
        String responseData = String.Empty;
    
        // Read the first batch of the TcpServer response bytes.
        Int32 bytes = stream.Read(data, 0, data.Length);
        responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
        Console.WriteLine("Received: {0}", responseData);
    
        // Close everything.
        stream.Close();
        client.Close();
      }
      catch (ArgumentNullException e)
      {
        Console.WriteLine("ArgumentNullException: {0}", e);
      }
      catch (SocketException e)
      {
        Console.WriteLine("SocketException: {0}", e);
      }
    
      Console.WriteLine("\n Press Enter to continue...");
      Console.Read();
    }

// Front end XAML

    <StackPanel Grid.Row="1" Orientation="Horizontal">
                <Button Padding="15 5" Margin="5" Content="Start"
                        Command="{x:Static local:WpfTimerWindow.StartCommand}"
                        CommandParameter="{x:Static local:WpfTimerWindow.Default}"/>

// Back end C# WPF

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    using System.Windows.Threading;
    using System.Diagnostics;
    using System.Timers;
    using System.ComponentModel;
    
    namespace PingApplication
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class WpfTimerWindow : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler? PropertyChanged;
    
            private readonly Timer timer = new Timer();
    
            private readonly Stopwatch stopwatch = new Stopwatch();
    
            public DateTime Now => DateTime.Now;
    
            public TimeSpan Elasped => stopwatch.Elapsed;
            public WpfTimerWindow()
            {
                //InitializeComponent();
                timer.Interval = 50;
                timer.Elapsed += OnTick;
                timer.Start();
                stopwatch.Start();
            }
    
            private void OnTick(object? sender, ElapsedEventArgs e)
            {
                if (PropertyChanged is PropertyChangedEventHandler propertyChanged)
                {
                    propertyChanged(this, NowArgs);
                    propertyChanged(this, ElaspedArgs);
                }
            }
    
            public static PropertyChangedEventArgs NowArgs { get; } = new PropertyChangedEventArgs(nameof(Now));
            public static PropertyChangedEventArgs ElaspedArgs { get; } = new PropertyChangedEventArgs(nameof(Elasped));
    
            public static RoutedUICommand StartCommand { get; } = new RoutedUICommand("Timer Start", "TimerStart", typeof(WpfTimerWindow));
            
            public static RoutedUICommand ResetCommand { get; } = new RoutedUICommand("Timer Stop", "TimerStop", typeof(WpfTimerWindow));
    
            public static ExecutedRoutedEventHandler ExecuteCommand { get; } = (_, e) =>
            {
                if (e.Parameter is WpfTimerWindow timer)
                {
                    if (e.Command == StartCommand)
                    {
                        timer.stopwatch.Start();
                    }
                    else if (e.Command == ResetCommand)
                    {
                        timer.stopwatch.Stop();
                    }
                   
                    
                    else return;
                    timer.OnTick(null, null);
                }
            };
            public static CanExecuteRoutedEventHandler CanExecuteCommand { get; } = (_, e) =>
            {
                if (e.Parameter is WpfTimerWindow timer)
                {
                    if (e.Command == StartCommand)
                    {
                        e.CanExecute = !timer.stopwatch.IsRunning;
                    }
                    else if (e.Command == ResetCommand)
                    {
                        e.CanExecute = timer.stopwatch.IsRunning;
                    }
                 
                }
            };
    
            public static WpfTimerWindow Default { get; } = new WpfTimerWindow();
        }
    }

Solution

  • General

    Try to create separate model:

    public class MyModel
    {
        public Guid Guid { get; set; }
        public DateTime Started { get; set; }
        public DateTime? Processed { get; set; }
        public DateTime? Elapsed { get; set; }
    }
    

    Usage (in your client code):

    ...
    var myModel = new MyModel
    {
        Guid = Guid.NewGuid(),
        Started = DateTime.Now,
    };
    var message = Newtonsoft.Json.JsonConvert.SerializeObject(myModel);
    var stream = client.GetStream();
    var buffer = Encoding.Unicode.GetBytes(message);
    stream.Write(buffer, 0, buffer.Length);
    stream.Flush();
    ...
    

    Usage (in your another client or server code):

    ...
    var stream = client.GetStream();
    var buffer = new byte[1024];
    var response = new StringBuilder();
    var bytes = stream.Read(buffer, 0, buffer.Length);
    
    if (bytes != 0)
    {
        response.Append(Encoding.Unicode.GetString(buffer, 0, bytes));
    
        while (stream.DataAvailable)
        {
            bytes = stream.Read(buffer, 0, buffer.Length);
            response.Append(Encoding.Unicode.GetString(buffer, 0, bytes));
        }
    
        var myModel = Newtonsoft.Json.JsonConvert.DeserializeObject<MyModel>(response);
        myModel.Processed = DateTime.Now;
        myModel.Elapsed = myModel.Processed - myModel.Started;
    
        // add the myModel to UI
        // In case of main thread issues, use App.Current.Dispatcher.Invoke(() => { addition here });
    }
    ...
    

    To develop more complex solution refer to this article about multi-threaded TCPSocket chat server.

    Application

    Based on the given requirements, we need to create two separate projects (e.g. WPFServer and WPFClient).

    WPFClient

    The WPFClient project has to include "client code", whenever you want, either in xaml.cs or even in a separate service class (e.g. MyCustomClientSocketService). Notice that you have to have an access to the TcpClient functionality.

    WPFServer

    So-called echo-tool (according to the question). The project includes "server code". Generally it can be put right in the xaml.cs, but creating separate service class can simplify and increase maintainability of the feature. Take into account that we have to broadcast the incoming message to available clients after the addition (// add the myModel to UI), therefore consider to store connected clients in System.Collections.Concurrent.ConcurrentDictionary. I highly recommend to explore the code from the stated article.