Skip to content

Getting Started (rapid develop your first server client infrastructure with SignalGo)

Gerardo Sista edited this page Feb 9, 2018 · 16 revisions

In this tutorial we will learn how to use SignalGo to create easily a server-client basic infrastructure. It's for C# developers but users can easily translate code in others .NET supported languages as VB.NET for example. Both client and server have an UI (windows forms) user can interact with (usually server runs as Console app or in a windows service). There's absolutely no difficult to switch to WPF UI, Windows Service or Console App instead of Windows Forms: the purpose of this tutorial is to teach how to set up and run a SignalGo infrastructure so user can use best suitable technology he needs.

STEP 1: what we want to build

We will create a server-client infrastructure who can perform these simple basic tasks for example:

  1. User can manually start server on Server UI clicking a button "Start server"
  2. User can connect to server by clicking a button "Connect" on client UI
  3. Client calls some methods on Server receiving results.
  4. Server calls a method on a specific client receiving results.
  5. User kills, from Server, one or more clients by clicking "Kill client" button

STEP 2: create the solution

In Visual Studio create a new Solution with 2 Windows Forms projects (one for client and one for server) and 1 Shared project (needed for a bunch of necessary interfaces)

Solution ServerClientSignalGo:

  1. ServerSignalGo (Windows Forms App)
  2. ClientSignalGo (Windows Forms App)
  3. SharedSignalGo (Shared Project)

STEP 3: install the SignalGo packages from NuGet

Projects in solution need to be added the appropriate SignalGo NuGet packages. Right click on the project and select "manage NuGet packages" to open the UI package manager where you can search the package to install.

For ServerSignalGo project search and install:

  • SignalGo.Net.Server
  • SignalGo.Net.Shared

For ClientSignalGo project search and install:

  • SignalGo.Net.Client
  • SignalGo.Net.Shared

For SharedSignalGo project:

  • No SignalGo NuGet package needed

STEP 4: prepare UI for ServerSignalGo

Add 3 buttons (start server, kill clients) and a richtextbox (to log events and results) on the Form.

STEP 5: prepare UI for ClientSignalGo

Add 2 buttons (connect to server, get list of persons), a richtextbox (to log events and results) on the Form and a datagridview (to show a list of complex object, in this case a list of Person.

STEP 6: General schema of solution

Before reading next steps, we have to learn the following basic schema about SignalGo mechanism: we must always write 2 interfaces (3 if we want SignalGo handle automatically awaitable methods )

  • 2 are for methods that client calls on server ( 1 for the async version of methods, if needed).

    IServerMethodsServerSide this defines sync methods (mandatory)
    
    IServerMethodsClientSide this is optional. It inherits from IServerMethodsServerSide and here we defines **only** async version of methods written in IServerMethodsServerSide. SignalGo will execute these methods on another thread to prevent UI block for long running tasks. Usage will be: var result = await MyServerService.MyMethodAsync(params)). Without this interface the same result can be obtained in client code using something like this: var result = await Task.Run(() => MyServerService.MySyncMethod(params));
    
  • 1 is for methods that server calls on client

    IClientMethods this defines method prototypes that server can call on client to get a result from it.
    

We then will write, in ServerSignalGo project, the class ServerMethods that inherits from IServerMethodsServerSide in ClientSignalGo project, the class ClientMethods that inherits from IClientMethods

All method names written in this classes must correspond, obviously, to the ones written in interfaces. We must only write Sync version of methods since SignalGo will handle the correct async version for us (writing async versions will also throw an exception since json can't serialize task objects)

Summarizing, this is how our solution appears with classes and interfaces needed by SignalGo:

ServerClientSignalGo solution

  • ServerSignalGo project (ServerMethods class)
  • ClientSignalGo project (ClientMethods class)
  • SharedSignalGo project (IServerMethodsServerSide, IServerMethodsClientSide, IClientMethods interfaces)

STEP 7: write classes and Interfaces into the SharedSignalGo project

This is the magic of SignalGo, we will go deep into the mechanism in a separate document. Now, in this example, we want to perform these tasks:

  • client must call a method on server that returns to client this message "Hello from Server to client =>[Your client's machine name]" (output example: "Hello from Server to client => PC-GERSIS"
  • before returning the message to client, server must ask to client it's machine name to get proper [Your client's machine name], assemble the message and then return it to the client
  • client then call a method on server that returns to client a list

7.1 Add the Person class to SharedSignalGo (needed in this example)

We need it later for the method that returns List to client. You can addo it also to other project accessible to both server and client. It can be also a class coming from Entity Framework for example.

    public class Person
    {
        public string Name { get; set; }
        public string Surname { get; set; }
        public string Address { get; set; }
        public int Age { get; set; }
        public System.Drawing.Bitmap Picture { get; set; }
    }

7.2 Add the interface IServerMethodsServerSide to SharedSignalGo for server methods executed synchronously

This interface defines method prototypes that will be executed server side. In this example methods that will be called by clients are:

  • one method HelloFromServer() that returns the string "Hello from Server to client => [Your client's machine name]" to client.
  • one method SendList() that returns the List to client.
using SignalGo.Shared.DataTypes;
using System.Collections.Generic;
  namespace SharedSignalGo
{
    [ServiceContract("ServerService", InstanceType.SingleInstance)]
    public interface IServerMethodsServerSide
    {
        //methods here will run server side always asyncronously in a separate thread
        string HelloFromServer();
        List<Person> SendList();
    }
} 

7.3 If you want let SignalGo handle automatically client UI responsiveness allowing you to use something like var result = await MyServerService.MyMethodAsync(params), add the interface IServerMethodsClientSide to SharedSignalGo for server methods executed asynchronously

This optional interface is needed to avoid UI freezing on client while executing method on server. It can inherits from IServerMethodsServerSide so we have only to write async prototypes versions of long running methods you want to be executed asynchronously. In this case, assuming that SendList() is a long running task, you must write the async version here putting the ASYNC suffix to the original name declared as sync. So, the function SendList() becomes Task<List> SendListAsync() in this new interface. Here's the code:

using SignalGo.Shared.DataTypes;
using System.Collections.Generic;
namespace SharedSignalGo
{
    [ServiceContract("ServerService", InstanceType.SingleInstance)]
    public interface IServerMethodsClientSide: IServerMethodsServerSide 
    {
       //This is the async version of SendList method. SignalGo detects the "Async" word and makes the SendList method 
       //awaitable. Usage: var listPersons = await ServerService.SendListAsync(); 
        Task<List<Person>> SendListAsync();
    }
} 

When using in main App, service must register always IServerMethodsClientSide interface! Since it inherits from IServerMethodsServerSide SignalGo will have both the sync and async versions of your methods! If you don't need async versions of your methods, we omit to write IServerMethodsClientSide and, obviously, we must register IServerMethodsServerSide interface.

7.4 Add the interface IClientMethods to SharedSignalGo for client methods

This interface defines method prototypes that will be executed on client by a server call. In this example the only method that will be called by server is:

  • GetMachineName that returns string System.Environment.MachineName of the client back to the server
    the method to kill clients is internally defined in SignalGo (CloseClient method), no need to write it.
using SignalGo.Shared.DataTypes;
namespace SharedSignalGo
{
    [ServiceContract("ClientService")]
    public interface IClientMethods
    {
        string GetMachineName();
    }
}

STEP 8: write classes into the ServerSignalGo project

Write the class ServerMethods, inheriting from IServerMethodsServerSide.

using SharedSignalGo;
using System.Collections.Generic;
using System.Drawing;
using System;
using SignalGo.Server.Models;
namespace ServerSignalGo
{
    public class ServerMethods : IServerMethodsServerSide
    {
        // Server Side all methods must be not Async since SignalGo will automatically hanlde all in a separate thread
        // Also task<T> objects cannot be serialized

        public string HelloFromServer()
        {
            try
            {
                //SERVER HERE CALLS GetMachineName ON THE CLIENT WHO INVOKED HelloFromServer METHOD !!!!!
                var callback = OperationContext.Current.GetClientCallback<IClientMethods>();
                string result = callback.GetMachineName();
                // END SERVER CALL

                return "Hello from Server to client => " + result;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public List<Person> SendList()
        {
            try
            {
                List<Person> list_persons = new List<Person>();
                for (int i = 0; i < 5; i++)
                {
                    string img64 = "[a valid base64 encoded image here]";
                    byte[] bytes = Convert.FromBase64String(img64);
                    Image image;
                    using (System.IO.MemoryStream ms = new System.IO.MemoryStream(bytes))
                    {
                        image = Image.FromStream(ms);
                    }
                    Person pr = new Person
                    {
                        Name = "Name_" + i,
                        Surname = "Surname_" + i,
                        Address = "Adress_" + i,
                        Age = i,
                        Picture = new Bitmap(image)
                    };
                    list_persons .Add(pr);
                }
                return list_persons ;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
}

Please note that **HelloFromServer** contains also a client callback!!!! In fact the server, before giving the result back to client, asks the client to give back it's machine name!!!! This is a good example of SignalGo powerful bi-directional data exchange!  

STEP 9: write classes into the ClientSignalGo project

Write the class ClientMethods, inheriting from IClientMethods.

using SharedSignalGo;
namespace ClientSignalGo
    {
    public class ClientMethods : IClientMethods
        {
        public string GetMachineName(int param)
            {
            return System.Environment.MachineName;
            }  
        }
    }

STEP 10: write events for ServerSignalGo project

To start the server, handle the click event of button "start server"

using SharedSignalGo;
using SignalGo.Server.Models;
using SignalGo.Server.ServiceManager;
// form code here...
  
// we declare a new server provider 
ServerProvider server = new ServerProvider();
 
private void btn_start_server_Click(object sender, EventArgs e)
        {
            try
            {
//no need to start the server if it's already running
                if (st_main.server.IsStarted == true) 
                {
                    return;
                }
                else
                {
                    //initialize the service registering methods that can be executed on server by clients
                    server.InitializeService<ServerMethods>();
                    richtextbox_server.AppendText("Server starting..." + Environment.NewLine);
                    //we start the server
                    server.Start("http://192.168.1.12:8080/AppSignalGo");
                    //set up some settings
                    server.InternalSetting = new SignalGo.Server.Settings.InternalSetting() { IsEnabledDataExchanger = true };
                    richtextbox_server.AppendText("Server started " + Environment.NewLine);
                    //register client callback: the methods the server can execute on clients
                    server.RegisterClientCallbackInterfaceService<IClientMethods>();
                    richtextbox_server.AppendText("Waiting for incoming connections" + Environment.NewLine);
                   }
            }
            catch (Exception ex)
            {
                richtextbox_server.AppendText("Error: " + ex.Message + Environment.NewLine);
            }
        }

To kill one specific client (the first in this example) we must handle the "kill clients" button click event

  private void btn_kill_Click(object sender, EventArgs e)
         {
            //get the context manually
            SignalGo.Server.Models.OperationContext context = new SignalGo.Server.Models.OperationContext();
            //server is your ServerProvider instance
            context.ServerBase = st_main.server;
            var AllClientsConnected = context.AllServerClients;
            var OneClientRandom = (from p in AllClientsConnected select p).FirstOrDefault();
            if (OneClientRandom != null)
            {
                st_main.server.CloseClient(OneClientRandom.ClientId);
                richtextbox_server.AppendText("Client: " + OneClientRandom.ClientId + " killed succesfully" + Environment.NewLine);
            }
        }

STEP 11: write events for ClientSignalGo project

To connect to the server, handle the click event of button "connect to server"

using SharedSignalGo;
// form code here...
  ClientMethods callbacks;
  IServerMethodsClientSide ServerService;
  ClientProvider connector = new ClientProvider();
 string serverAdress = "http://192.168.1.12:8080/AppSignalGo";
  private async void btn_connect_ClickAsync(object sender, EventArgs e)
        {
            {
                try
                {
                    richTexBoxClient.AppendText("Connecting to Server..." + Environment.NewLine);
                  //this action prevent freezing the UI  
                  await Task.Run((Action)(() =>
                    {
                        connector.Connect(serverAdress );
                    }));
                    richTexBoxClient.AppendText("Registering services" + Environment.NewLine);
                    //register the server callback
                    callbacks = connector.RegisterServerCallback<ClientMethods>();
                    //register the interface defining the async methods
                    ServerService = connector.RegisterClientServiceInterfaceWrapper<IServerMethodsClientSide>();
                    richTexBoxClient.AppendText("Services registered" + Environment.NewLine);
                    richTexBoxClient.AppendText("Connection esablished with " + serverAdress  + " Your ID is " + connector.ClientId + Environment.NewLine);
                    //we call the method on server and print it into the richtextbox once received
                    var res = ServerService.HelloFromServer();
                    richTexBoxClient.AppendText(res + Environment.NewLine);
                    //in this action wwe handle OnDisconnected action of this client
                    connector.OnDisconnected = () =>
                     {
                         this.Invoke((Action)(() =>
                         {
                             {
                                //non MDI form: close will dispose too
                                 this.Close();
                             }
                         }));
                     };
                }
                catch (Exception ex)
                {
                    richTexBoxClient.AppendText(ex.Message + Environment.NewLine);
                }
            }
        }

To retrieve the list of persons from the server, handle the click event of button "get list of persons"

  private async void btn_persons_ClickAsync(object sender, EventArgs e)
        {
            richTexBoxClient.AppendText("Receiving Persons data. Wait..." + Environment.NewLine);
            //we call the async version of SendList on server. This prevent UI freezing while running this task on server
            var listPersons = await ServerService.SendListAsync();
            richTexBoxClient.AppendText("Persons received" + Environment.NewLine);
            //instantiate a bindingsource and bind list<Person> to it
            BindingSource bsource = new BindingSource
            {
                DataSource = listPersons
            };
            // then bind the bindingsource to the datagridview to visualize resutls
            dataGridViewPersons.DataSource = bsource;
        }

Let's handle resource release on closing the main form:

   private void F_client_FormClosing(object sender, FormClosingEventArgs e)
        {
           connector.Dispose();
        }
Clone this wiki locally