aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.Sites/Views/MainView.xaml
blob: 4ca82c4f73f248df7551be838b2a569f926586b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<UserControl x:Class="Tango.MachineStudio.Sites.Views.MainView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Tango.MachineStudio.Sites.Views"
             xmlns:vm="clr-namespace:Tango.MachineStudio.Sites.ViewModels"
             xmlns:global="clr-namespace:Tango.MachineStudio.Sites"
             xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
             xmlns:controls="clr-namespace:Tango.SharedUI.Controls;assembly=Tango.SharedUI"
             mc:Ignorable="d" 
             d:DesignHeight="1080" d:DesignWidth="1920" Background="Transparent" d:DataContext="{d:DesignInstance Type=vm:MainViewVM, IsDesignTimeCreatable=False}" DataContext="{x:Static global:ViewModelLocator.MainViewVM}">
    <Grid IsEnabled="{Binding IsFree}">
        <controls:NavigationControl x:Name="navigationControl" TransitionType="Slide">
            <local:SitesView />
            <local:SiteDetailsView/>
        </controls:NavigationControl>
    </Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Google.Protobuf;
using Tango.BL.Entities;
using Tango.Integration.Operation;
using Tango.PMR;
using Tango.PMR.Common;
using Tango.Transport;
using Tango.Transport.Transporters;
using Tango.PMR.Integration;
using Tango.Transport.Discovery;
using Tango.Transport.Servers;
using Tango.Transport.Adapters;
using Tango.PMR.Connection;
using Tango.PMR.Diagnostics;
using Tango.PMR.Debugging;
using System.Security.Authentication;
using Tango.Settings;
using Tango.Core.ExtensionMethods;
using Tango.PMR.MachineStatus;
using Tango.PMR.Power;
using Tango.Core;
using Microsoft.AspNet.SignalR.Client;
using Tango.Integration.ExternalBridge.Web;
using Tango.Core.Threading;
using System.Diagnostics;
using System.Reflection;
using Newtonsoft.Json;

namespace Tango.Integration.ExternalBridge
{
    public class ExternalBridgeService : ExtendedObject, IExternalBridgeService
    {
        private List<ExternalBridgeReceiver> _receivers;
        private UdpDiscoveryService<ExternalBridgeUdpDiscoveryPacket> _discoveryService;
        private TcpServer _tcpServer;
        private int _discovery_port = 8888; //Will be overridden by settings in constructor..
        private int _external_bridge_port = 1984; //Will be overridden by settings in constructor..
        private HubConnection _connection;
        private IHubProxy _proxy;
        private bool _isSignalRConnected;
        private System.Timers.Timer _signalrPingTimer;
        private Dictionary<String, RequestHandler> _requestHandlers;
        private static JsonSerializerSettings _genericMessageSettings = new JsonSerializerSettings()
        {
            TypeNameHandling = TypeNameHandling.All
        };

        private class RequestHandler
        {
            public IExternalBridgeRequestHandler Handler { get; set; }
            public MethodInfo Method { get; set; }
            public RequestHandlerLoggingMode LoggingMode { get; set; }
        }

        #region Events

        /// <summary>
        /// Occurs when a new client is waiting for authentication.
        /// </summary>
        public event EventHandler<ExternalBridgeClientConnectedEventArgs> ConnectionRequest;

        /// <summary>
        /// Occurs when the last client has been disconnected.
        /// </summary>
        public event EventHandler FullControlSessionDisconnected;

        /// <summary>
        /// Occurs when the service has received a new color profile request.
        /// </summary>
        public event EventHandler<ColorProfileRequestEventArgs> ColorProfileRequest;

        #endregion

        #region Properties

        public static HashSet<MessageType> SafetyLevelOperations { get; private set; }

        /// <summary>
        /// Gets or sets the TCP transport adapter write mode when creating a TCP receiver.
        /// </summary>
        public TcpTransportAdapterWriteMode TcpTransportAdapterWriteMode { get; set; }

        /// <summary>
        /// Gets or sets the SignalR configuration.
        /// </summary>
        public ExternalBridgeSignalRConfiguration SignalRConfiguration { get; set; }

        private IMachineOperator _machineOperator;
        /// <summary>
        /// Gets or sets the machine operator.
        /// </summary>
        public IMachineOperator MachineOperator
        {
            get { return _machineOperator; }
            set { _machineOperator = value; OnMachineOperatorChanged(); }
        }

        private Machine _machine;
        /// <summary>
        /// Gets or sets the machine.
        /// </summary>
        public Machine Machine
        {
            get { return _machine; }
            set { _machine = value; OnMachineChanged(); }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is started.
        /// </summary>
        public bool IsStarted { get; private set; }

        private bool _enabled;
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="IExternalBridgeService" /> is enabled.
        /// </summary>
        public bool Enabled
        {
            get { return _enabled; }
            set
            {
                _enabled = value;

                if (_enabled)
                {
                    Start();
                }
                else
                {
                    Stop();
                }

                RaisePropertyChangedAuto();
            }
        }

        private bool _hasSessions;
        /// <summary>
        /// Gets a value indicating whether there are any connected sessions.
        /// </summary>
        public bool HasSessions
        {
            get { return _hasSessions; }
            private set { _hasSessions = value; RaisePropertyChangedAuto(); }
        }

        private ExternalBridgeReceiver _fullControlSessionReceiver;
        /// <summary>
        /// Gets the current full control session receiver.
        /// </summary>
        public ExternalBridgeReceiver FullControlSessionReceiver
        {
            get { return _fullControlSessionReceiver; }
            private set { _fullControlSessionReceiver = value; RaisePropertyChangedAuto(); }
        }


        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the <see cref="ExternalBridgeService"/> class.
        /// </summary>
        static ExternalBridgeService()
        {
            SafetyLevelOperations = new HashSet<MessageType>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExternalBridgeService"/> class.
        /// </summary>
        public ExternalBridgeService()
        {
            _requestHandlers = new Dictionary<string, RequestHandler>();

            SignalRConfiguration = new ExternalBridgeSignalRConfiguration();

            TcpTransportAdapterWriteMode = TcpTransportAdapterWriteMode.Interval;

            _receivers = new List<ExternalBridgeReceiver>();

            var settings = SettingsManager.Default.GetOrCreate<IntegrationSettings>();

            _discovery_port = settings.ExternalBridgeServiceDiscoveryPort;
            _external_bridge_port = settings.ExternalBridgeServicePort;

            _tcpServer = new TcpServer(_external_bridge_port);
            _tcpServer.ClientConnected += _tcpServer_ClientConnected;

            LogManager.NewLog += LogManager_NewLog;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExternalBridgeService"/> class.
        /// </summary>
        /// <param name="machineOperator">The machine operator.</param>
        /// <param name="machine">The machine.</param>
        public ExternalBridgeService(IMachineOperator machineOperator, Machine machine) : this()
        {
            Machine = machine;
            MachineOperator = machineOperator;
        }

        #endregion

        #region Properties Changes

        /// <summary>
        /// Called when the machine has been changed
        /// </summary>
        protected virtual void OnMachineChanged()
        {
            if (_discoveryService != null && _discoveryService.IsStarted)
            {
                _discoveryService.Stop();
            }

            _discoveryService = new UdpDiscoveryService<ExternalBridgeUdpDiscoveryPacket>(_discovery_port, new ExternalBridgeUdpDiscoveryPacket()
            {
                SerialNumber = Machine.SerialNumber,
            });

            _discoveryService.BeforeBroadcasting -= _discoverySevice_BeforeBroadcasting;
            _discoveryService.BeforeBroadcasting += _discoverySevice_BeforeBroadcasting;
        }

        private void _discoverySevice_BeforeBroadcasting(object sender, ExternalBridgeUdpDiscoveryPacket e)
        {
            e.Time = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss");
        }

        /// <summary>
        /// Called when the machine operator has been changed
        /// </summary>
        protected virtual void OnMachineOperatorChanged()
        {
            if (MachineOperator != null)
            {
                MachineOperator.StatusChanged -= MachineOperator_StatusChanged;
                MachineOperator.StatusChanged += MachineOperator_StatusChanged;
                MachineOperator.PendingResponseReceived -= MachineOperator_PendingResponseReceived;
                MachineOperator.PendingResponseReceived += MachineOperator_PendingResponseReceived;
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the ClientConnected event of the _tcpServer control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="ClientConnectedEventArgs"/> instance containing the event data.</param>
        private async void _tcpServer_ClientConnected(object sender, ClientConnectedEventArgs e)
        {
            LogManager.Log("External bridge TCP client connected from: " + e.Socket.GetIPAddress());
            ExternalBridgeReceiver receiver = new ExternalBridgeReceiver(e.Socket, MachineOperator);
            (receiver.Adapter as TcpTransportAdapter).WriteMode = TcpTransportAdapterWriteMode;
            receiver.LoginRequest += Receiver_LoginRequest;
            receiver.ColorProfileRequest += Receiver_ColorProfileRequest;
            receiver.Disconnected += Receiver_Disconnected;
            receiver.ReceiverRequestReceived += Receiver_ReceiverRequestReceived;
            _receivers.Add(receiver);
            await receiver.Connect();
        }

        private async void OnSignalRSessionCreated(String sessionID)
        {
            try
            {
                LogManager.Log("External bridge SignalR client connected.");

                var adapter = new SignalRTransportAdapter(SignalRConfiguration.Address, SignalRConfiguration.Hub, SignalRTransportAdapterMode.JoinSession, Machine.SerialNumber, sessionID); ;

                ExternalBridgeReceiver receiver = new ExternalBridgeReceiver(adapter, MachineOperator);
                receiver.LoginRequest += Receiver_LoginRequest;
                receiver.ColorProfileRequest += Receiver_ColorProfileRequest;
                receiver.Disconnected += Receiver_Disconnected;
                receiver.ReceiverRequestReceived += Receiver_ReceiverRequestReceived;
                _receivers.Add(receiver);
                await receiver.Connect();
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error initializing SignalR ExternalBridgeReceiver when session created.");
            }
        }

        private void MachineOperator_StatusChanged(object sender, MachineStatuses status)
        {
            try
            {
                _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics).ToList().ForEach(x => x.UpdateMachineOperatorStatus((UpdateStatus)status));
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error updating status of external bridge service client.");
            }
        }

        private void LogManager_NewLog(object sender, Tango.Logging.LogItemBase e)
        {
            var toSend = _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresApplicationLogs).ToList();

            if (_receivers.Count > 0)
            {
                var msg = MessageFactory.CreateTangoMessage<StartApplicationLogsResponse>(new StartApplicationLogsResponse()
                {
                    LogItem = ByteString.CopyFrom(e.Serialize())
                });

                msg.ToBytes();

                toSend.ToList().Where(x => x.State == TransportComponentState.Connected).ToList().ForEach(x => x.UpdateApplicationLogs(msg.Container));
            }
        }

        private void MachineOperator_PendingResponseReceived(object sender, MessageContainer container)
        {
            OnOperatorResponseReceived(container);
        }

        #endregion

        #region Receiver Events Handlers

        private void Receiver_Disconnected(object sender, EventArgs e)
        {
            var receiver = sender as ExternalBridgeReceiver;

            receiver.LoginRequest -= Receiver_LoginRequest;
            receiver.ColorProfileRequest -= Receiver_ColorProfileRequest;
            receiver.Disconnected -= Receiver_Disconnected;
            receiver.ReceiverRequestReceived -= Receiver_ReceiverRequestReceived;

            _receivers.Remove(receiver);

            if (receiver.LoginIntent == ExternalBridgeLoginIntent.FullControl)
            {
                FullControlSessionDisconnected?.Invoke(this, new EventArgs());
            }

            HasSessions = _receivers.Count(x => x.IsLoggedIn) > 0;

            FullControlSessionReceiver = _receivers.SingleOrDefault(x => x.IsLoggedIn && x.LoginIntent == ExternalBridgeLoginIntent.FullControl);

            //Notify request handlers about this receiver disconnected.
            foreach (var requestHandler in _requestHandlers.ToList().Select(x => x.Value.Handler))
            {
                try
                {
                    requestHandler.OnReceiverDisconnected(receiver);
                }
                catch { } //Ignore exceptions on handler.
            }
        }

        private void Receiver_ColorProfileRequest(object sender, ColorProfileRequestEventArgs e)
        {
            ColorProfileRequest?.Invoke(this, e);
        }

        private void Receiver_LoginRequest(object sender, ExternalBridgeReceiverLoginRequestEventArgs e)
        {
            var request = e.Request;

            if (request.Intent == ExternalBridgeLoginIntent.FullControl && _receivers.Any(x => x.IsLoggedIn && x.LoginIntent == ExternalBridgeLoginIntent.FullControl))
            {
                e.Decline("Only one full control client is allowed.");
                return;
            }

            if (request.Intent == ExternalBridgeLoginIntent.FullControl && MachineOperator.IsPrinting)
            {
                e.Decline($"External bridge client login failed because the machine is currently printing and '{ExternalBridgeLoginIntent.FullControl}' intent was requested.");
                return;
            }

            ExternalBridgeClientConnectedEventArgs args = null;

            args = new ExternalBridgeClientConnectedEventArgs(() =>
            {
                //Confirmed
                e.Confirm(Machine, MachineOperator.DeviceInformation, args.ApplicationInformation);
                HasSessions = true;

                if (request.Intent == ExternalBridgeLoginIntent.FullControl)
                {
                    FullControlSessionReceiver = sender as ExternalBridgeReceiver;
                }
            }, (reason) =>
             {
                 //Declined
                 e.Decline(reason);
             });

            args.Request = request;
            args.Address = e.Address;
            ConnectionRequest?.Invoke(this, args);
        }

        private void Receiver_ReceiverRequestReceived(object sender, ExternalBridgeReceiverRequestReceivedEventArgs e)
        {
            if (e.Container.Type == MessageType.GenericRequest || _requestHandlers.ContainsKey(e.Container.Type.ToString()))
            {
                e.Handled = true;

                ThreadFactory.StartNew(() =>
                {
                    try
                    {
                        var message = MessageFactory.ExtractMessageFromContainer(e.Container);

                        if (e.Container.Type != MessageType.GenericRequest) //Handle standard PMR messages.
                        {
                            var handler = _requestHandlers[e.Container.Type.ToString()];

                            if (handler.Method.ReturnType == typeof(Task))
                            {
                                ((Task)handler.Method.Invoke(handler.Handler, new object[]
                                {
                                   message,
                                   e.Container.Token,
                                   sender,
                                })).Wait();
                            }
                            else
                            {
                                handler.Method.Invoke(handler.Handler, new object[]
                                {
                                   message,
                                   e.Container.Token,
                                   sender,
                                });
                            }
                        }
                        else //Handle GenericRequest with inner JSON formated generic message.
                        {
                            var genericType = Type.GetType((message as GenericRequest).Type);

                            try
                            {
                                if (_requestHandlers.ContainsKey(genericType.FullName))
                                {
                                    var innerMessage = GenericMessageSerializer.DeserializeFromByteString(genericType, (message as GenericRequest).Data);

                                    var handler = _requestHandlers[genericType.FullName];

                                    if (handler.LoggingMode == RequestHandlerLoggingMode.LogRequestName || handler.LoggingMode == RequestHandlerLoggingMode.LogRequestNameAndContent)
                                    {
                                        String content = ".";

                                        if (handler.LoggingMode == RequestHandlerLoggingMode.LogRequestNameAndContent)
                                        {
                                            content = $":\n{innerMessage.ToJsonString()}";
                                        }

                                        LogManager.Log($"'{innerMessage.GetType().Name}' received on '{handler.Method.DeclaringType.Name}.{handler.Method.Name}'{content}");
                                    }

                                    if (handler.Method.ReturnType == typeof(Task))
                                    {
                                        ((Task)handler.Method.Invoke(handler.Handler, new object[]
                                        {
                                        innerMessage,
                                        e.Container.Token,
                                        sender,
                                        })).Wait();
                                    }
                                    else
                                    {
                                        handler.Method.Invoke(handler.Handler, new object[]
                                        {
                                            innerMessage,
                                            e.Container.Token,
                                            sender,
                                        });
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                LogManager.Log(ex.GetFirstIfAggregate(), $"Error invoking external bridge handler for request '{genericType.Name}'.");

                                try
                                {
                                    (sender as ITransporter).SendErrorResponse(ex.GetFirstIfAggregate(), e.Container.Token);
                                }
                                catch { }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, $"An error occurred while trying or invoking an external bridge request handler for '{e.Container.Type}'.");
                        try
                        {
                            (sender as ITransporter).SendErrorResponse(ex.GetFirstIfAggregate(), e.Container.Token);
                        }
                        catch { }
                    }
                });
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            if (!IsStarted)
            {
                LogManager.Log("Starting external bridge service...");

                LogManager.Log("Starting TCP server...");
                try
                {
                    _tcpServer.Start();
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error starting external bridge TCP server.");
                }

                LogManager.Log("Starting discovery service...");
                try
                {
                    _discoveryService.Start();
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error starting external bridge discovery service.");
                }

                if (SignalRConfiguration.Enabled)
                {
                    StartSignalR();
                }

                IsStarted = true;
                _enabled = true;
                RaisePropertyChanged(nameof(Enabled));
            }
        }

        private void StartSignalR()
        {
            if (!_enabled || _isSignalRConnected) return;

            try
            {
                try
                {
                    if (_connection != null)
                    {
                        _connection.Dispose();
                    }
                }
                catch { }

                LogManager.Log("Starting SignalR service...");

                _connection = new HubConnection(SignalRConfiguration.Address);
                _proxy = _connection.CreateHubProxy(SignalRConfiguration.Hub);
                _proxy.On<String>("OnSessionCreated", OnSignalRSessionCreated);
                _connection.Start();
                _connection.StateChanged += (x) =>
                {
                    if (x.NewState == ConnectionState.Connected)
                    {
                        try
                        {
                            LogManager.Log("External Bridge Service SignalR Connected. Registering machine...");

                            _proxy.Invoke("RegisterMachine", new MachineInfo()
                            {
                                SerialNumber = Machine.SerialNumber,
                                Organization = Machine.Organization.Name,
                            });

                            _isSignalRConnected = true;

                            if (_signalrPingTimer == null)
                            {
                                _signalrPingTimer = new System.Timers.Timer(TimeSpan.FromMinutes(5).TotalMilliseconds);
                                _signalrPingTimer.Elapsed += _signalrPingTimer_Elapsed;
                                _signalrPingTimer.Start();
                            }
                        }
                        catch (Exception ex)
                        {
                            LogManager.Log(ex, "Error registering machine via SignalR.");
                        }
                    }
                    else if (x.NewState == ConnectionState.Disconnected)
                    {
                        _isSignalRConnected = false;

                        if (Enabled)
                        {
                            LogManager.Log("External Bridge Service SignalR Disconnected/Failed. Reconnecting in 1 minute...");
                            TimeoutTask.StartNew(StartSignalR, TimeSpan.FromMinutes(1));
                        }
                    }
                    else if (x.NewState == ConnectionState.Reconnecting)
                    {
                        LogManager.Log("External Bridge Service SignalR Connection Lost. Reconnecting...");
                        //Will go invoke state change again with "connected" if successful...
                    }
                };
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error initializing External Bridge SignalR. Will try again in 5 minutes...");

                TimeoutTask.StartNew(StartSignalR, TimeSpan.FromMinutes(5));
            }
        }

        private async void _signalrPingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (Enabled && SignalRConfiguration.Enabled)
            {
                if (_proxy != null)
                {
                    try
                    {
                        var result = await _proxy.Invoke<String>("Ping");
                    }
                    catch
                    {
                        Debug.WriteLine("Error pinging to machine service via SignalR.");
                    }
                }
            }
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public async void Stop()
        {
            if (IsStarted)
            {
                try
                {
                    _tcpServer.Stop();
                    _discoveryService.Stop();
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error disposing TCP discovery services.");
                }

                foreach (var receiver in _receivers.ToList())
                {
                    try
                    {
                        await receiver.Disconnect();
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, $"Error disconnecting receiver {receiver.ComponentName}.");
                    }
                }

                _enabled = false;

                if (SignalRConfiguration.Enabled)
                {
                    try
                    {
                        _isSignalRConnected = false;
                        await _proxy.Invoke("UnregisterMachine");
                        _connection.Stop();
                        _connection.Dispose();
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error disposing SignalR connection.");
                    }
                }

                IsStarted = false;
                HasSessions = false;
                RaisePropertyChanged(nameof(Enabled));
            }
        }

        /// <summary>
        /// Disconnects the current remote client session.
        /// </summary>
        public async void DisconnectFullControlSession()
        {
            var sessionReceiver = _receivers.SingleOrDefault(x => x.IsLoggedIn && x.LoginIntent == ExternalBridgeLoginIntent.FullControl);
            if (sessionReceiver != null)
            {
                await sessionReceiver.Disconnect();
            }
        }

        #endregion

        #region Virtual Methods

        protected virtual void OnOperatorResponseReceived(MessageContainer container)
        {
            try
            {
                switch (container.Type)
                {
                    case MessageType.StartDiagnosticsResponse:
                        _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresDiagnostics).ToList().ForEach(x => x.UpdateDiagnostics(container));
                        break;
                    case MessageType.StartDebugLogResponse:
                        _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresDebugLogs).ToList().ForEach(x => x.UpdateDebugLogs(container));
                        break;
                    case MessageType.StartEventsNotificationResponse:
                        _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresEventsNotification).ToList().ForEach(x => x.UpdateEvents(container));
                        break;
                    case MessageType.StartMachineStatusUpdateResponse:
                        _receivers.ToList().Where(x => x.IsLoggedInAndRequiresDiagnostics && x.RequiresMachineStatusUpdate).ToList().ForEach(x => x.UpdateMachineStatus(container));
                        break;
                }

            }
            catch { }
        }

        #endregion

        #region Handlers Registration

        public void RegisterRequestHandler(IExternalBridgeRequestHandler handler)
        {
            foreach (var method in handler.GetType().GetMethods())
            {
                var att = method.GetCustomAttribute<ExternalBridgeRequestHandlerMethodAttribute>();
                if (att != null)
                {
                    _requestHandlers.Add(att.Type is IMessage ? att.Type.Name : att.Type.FullName, new RequestHandler()
                    {
                        Handler = handler,
                        Method = method,
                        LoggingMode = att.LoggingMode,
                    });
                }
            }
        }

        public void UnregisterRequestHandler(IExternalBridgeRequestHandler handler)
        {
            foreach (var h in _requestHandlers.Where(x => x.Value.Handler == handler).ToList())
            {
                _requestHandlers.Remove(h.Key);
            }
        }

        #endregion
    }
}