Server -III-

a-neutron-star-pours-its-heart-out (1)

In this post I continue building the basic server application. In previous post I created the service host project including the WCF service interfaces. They implement all operation contracts to establish incoming and outgoing connections. This time I continue with another project from the server solution, were all service related auxiliary  classes, interfaces and message types will have a home.

So lets start with adding a new class library project to the solution and name it ServiceAuxiliaries. I created folders for Interfaces, classes and messages where some elementary entities are living. You will see this will close some of the gaps from my previous Server -II- post.

Lets start with talking about the basic Interfaces I need to get some consistency into the service application.

Service Auxiliary Interfaces

First there is a IClientSession Interface:

using System;

namespace ServiceAuxiliaries
{
    /// defines methods and properties common for all sessions managed by DataServer
    public interface IClientSession
    {
        /// user name
        string Login { get; set; }
        /// session ID, must be unique
        string ID { get; set; }
        /// session object, generally session context
        object SessionObject { get; set; }
        /// send response message to client
        void Send(ResponseMessage aResponse);
        /// disconnect session
        void Disconnect();
        /// send heartbeat
        void Heartbeat();
        /// sends error info
        void SendError(Exception e);
    }
}

The IClientSession interface handles all basics about client login/logout and repons messages to client from our service. Also I force each class who implements the interface to create a session object for each client connection for better handling.

Second we have a ILogonControl interface:

using System.Collections.Generic;

namespace ServiceAuxiliaries.Interfaces
{
    public interface ILogonControl
    {
        /// get/set data feeder parameters
        Dictionary<string, object> Settings { get; set; }
        /// validate data feeder parameters
        void ValidateSettings();
    }
}

Here we get control over the service endpoint settings like IP address and Port numbers. Also we could provide some functionality to catch wrong entries like typos or other mistakes, by a ValidateSettings() method.

Third I created a IServiceHost interface which is the common interface for all service hosts running on the server. Imagine if you would like to add another host with different protocol like websockets or something else, you would use this interface to fit it into the server application.

using ServiceAuxiliaries.Interfaces;
using System.Collections.Generic;

namespace ServiceAuxiliaries
{
    /// common interface for all connection service hosts
    public interface IServiceHost
    {
        /// starts service
        /// <param name="aParameters">dictionary of parameters identified by a key name</param>
        void Start(Dictionary<string, object> aParameters);
        /// stops service
        void Stop();
        /// get dictionary of default parameters
        Dictionary<string, object> DefaultSettings { get; }
        /// name to identify service
        string Name { get; }
        /// returns object that implements ILogonControl interface. Generally, the object is UI control
        ILogonControl LogonControl { get; }
    }
}

 

Service Auxiliary Classes

Than I would add a folder named Classes where all auxiliary classes are stored. Currently we need just two classes to establish a basic service application:

using System.Runtime.Serialization;

namespace ServiceAuxiliaries
{
    [DataContract]
    public class ServerException
    {
        [DataMember]
        public string Reason { get; set; }

        public ServerException(string aMessage)
        {
            Reason = aMessage;
        }
    }
}

The ServerException is just a message of type string to handle all internal errors. This is be done by FaultException class from .Net ServiceModel. It was invoked by “FaultContract” attribute in WcfServise.cs from previous post.

Another important class is the MessageRouter class. All incoming and outgoing messages become events and be routed to their consumers.

using System;
using System.Collections.Generic;

namespace ServiceAuxiliaries
{
    /// The class implements functionality to route incoming and outgoing messages from connection service to feeder and vice-versa
    public class MessageRouter
    {
        /// global object that requires synchronized access
        public static MessageRouter gMessageRouter;

        /// message router event argument
        public class MessageRouter_EventArgs : EventArgs
        {
            /// unique ID, generally, session ID
            public readonly string ID;

            /// object that represents session/user info specified by ID
             public readonly IClientSession UserInfo;

            /// original incoming request message
            public readonly RequestMessage Request;

            /// true, if the request must be ignored
            public bool Cancel = false;

            /// reason to ignore the request
            public string Reason = String.Empty;

            /// constructor
            public MessageRouter_EventArgs(string aID, IClientSession aUserInfo)
            {
                this.ID = aID;
                this.UserInfo = aUserInfo;
                this.Request = null;
            }

            /// constructor
            public MessageRouter_EventArgs(string aID, IClientSession aUserInfo, RequestMessage aRequest)
            {
                this.ID = aID;
                this.UserInfo = aUserInfo;
                this.Request = aRequest;
            }
        }

        /// fired, when a new session added
        public event EventHandler<MessageRouter_EventArgs> AddedSession;

        /// fired, when a session removed
        public event EventHandler<MessageRouter_EventArgs> RemovedSession;

        /// fired, when a client request received
        public event EventHandler<MessageRouter_EventArgs> RouteRequest;

        /// current active sessions
        protected Dictionary<string, IClientSession> m_ActiveSessions;

        public MessageRouter()
        {
        }

        /// get session/user info by ID
        public virtual IClientSession GetClientSessionInfo(string aID)
        {
            IClientSession aUserInfo;

            lock (m_ActiveSessions)
            {
                if (m_ActiveSessions.TryGetValue(aID, out aUserInfo))
                    return aUserInfo;
                else
                    return null;
            }
        }

        /// get user info by an object, generally, the object is session context
        public virtual IClientSession GetClientSessionInfo(object obj)
        {
            lock (m_ActiveSessions)
            {
                foreach (IClientSession item in m_ActiveSessions.Values)
                {
                    if (item.SessionObject != null && object.ReferenceEquals(item.SessionObject, obj))
                        return item;
                }
            }
            return null;
        }

        /// returns unique ID if logon is complete
        public bool Authenticate(string aLogin, string aPassword)
        {
            return true;
        }

        /// adds a session identified by uniques ID
        public virtual void AddSession(string aID, IClientSession aUserInfo)
        {
            lock (m_ActiveSessions)
            {
                var args = new MessageRouter_EventArgs(aID, aUserInfo);

                if (!args.Cancel)
                {
                    m_ActiveSessions.Add(aID, aUserInfo);
                    if (AddedSession != null)
                        AddedSession(this, new MessageRouter_EventArgs(aID, aUserInfo));
                }
                else
                    throw new ApplicationException("The session is not enabled. Reason: " + args.Reason);
            }
        }

        /// removes session by ID
        public virtual IClientSession RemoveSession(string aID)
        {
            IClientSession aUserInfo;

            lock (m_ActiveSessions)
            {
                if (m_ActiveSessions.TryGetValue(aID, out aUserInfo))
                {
                    m_ActiveSessions.Remove(aID);
                    if (RemovedSession != null)
                        RemovedSession(this, new MessageRouter_EventArgs(aID, aUserInfo));

                    return aUserInfo;
                }

                return null;
            }
        }

        /// routes incoming request
        /// </summary>
        public virtual void ProcessRequest(string aID, RequestMessage aRequest)
        {
            IClientSession aUserInfo = GetClientSessionInfo(aID);

            if (aUserInfo != null)
            {
                if (this.RouteRequest != null)
                    this.RouteRequest(this, new MessageRouter_EventArgs(aID, aUserInfo, aRequest));
            }
        }

        /// dispose object
        public void Dispose()
        {
            lock (m_ActiveSessions)
            {
                m_ActiveSessions.Clear();
                m_ActiveSessions = null;
            }
        }
    }
}

The MessageRouter cares about the messages as events and delivers the required information or parameters to the right place. Currently this is about add/remove client session, heartbeat beacon and error handling only, but later we it becomes extended by broker or exchange accounts.

Now lets care about some basic message types for request and response messages, to become routed by the MessageRouter. In this post we start with some message types for login, error and heartbeat messages. This just provides core  functions of the server client application. I had put them in two separate folders one for request messages and another for response messages.

Service Auxiliary Messages

The RequestMessages are send from client to our service:

using System.Runtime.Serialization;

namespace ServiceAuxiliaries
{
    [DataContract]
    public class RequestMessage
    {

        /// identifies user/session info where the request received
        [IgnoreDataMember]
        public IClientSession User { get; set; }
    }
}

From the RequestMessage class all request messages becoming derived. It just contains a property of type IClientSession with all the client information to identify where the request comes from.

 

using System.Runtime.Serialization;

namespace ServiceAuxiliaries
{
    [DataContract]
    public class LoginRequest : RequestMessage
    {
        /// login, generally user name
        [DataMember]
        public string Login;

        /// password
        [DataMember]
        public string Password;

        public LoginRequest(string aLogin, string aPassword)
        {
            Login = aLogin;
            Password = aPassword;
        }
    }
}

The logon information are transmitted by the LoginRequest message. The Login() method from WCFService.cs calls the authentication method from MessageRouter class to verify the login request

 

The ResponseMessages are send from service back to client as answer:

using System.Runtime.Serialization;

namespace ServiceAuxiliaries
{
    [DataContract]
    public class ResponseMessage
    {
        /// user/session info to send response
         public IClientSession User { get; set; }
    }
}

From the ResponseMessage class all response messages are derived. It just contains a property of type IClientSession with all the client information to identify where the response will be targeted to.

using System.Runtime.Serialization;

namespace ServiceAuxiliaries
{
    [DataContract]
    public class LoginResponse : ResponseMessage
    {
    }
}

The LoginResponse message gets no further implementation here, but it provides to be the type of  Login() method in IWCFService interface. There its staffed by [OperationContract] attribute so it becomes processed if a login request arrives. It still contains all the client information due to it derived from ResponseMessage class. The implementation we had already done in WCFService class.

using System;
using System.Runtime.Serialization;

namespace ServiceAuxiliaries
{
    [DataContract]
    public class ErrorInfoResponse : ResponseMessage
    {
        public ErrorInfoResponse(Exception e)
        {
            Error = e.Message;
        }

        [DataMember]
        public string Error { get; set; }
    }
}

The ErrorInfoResponse message is build into the IWCFService interface and there it provides sending error messages by default.

using System.Runtime.Serialization;

namespace ServiceAuxiliaries
{
    [DataContract]
    public class HeartbeatResponse : ResponseMessage
    {
        [DataMember]
        public string Text { get; set; }

        public HeartbeatResponse (string aText)
        {
            this.Text = Text;
        }
    }
}

The HeartbeatResponse message is build into the IWCFService interface and is a beacon which could be used by a client to monitor whether he is still connected.

For now, this covers all of the basic auxiliary classes, interfaces and messages and provides the main functionality to establish a server client communication. Of course, its necessary to add the reference of the ServiceAuxiliaries project to the WcfServiceHost project!

The next post will get all this messages in a row by message queues and threads and register the right events during startup. This is done in a third project which later becomes the startup project, so stay tuned! That’s for now and I hope you like to see how everything will get together. If you have suggestions or comments, feel free to let me know. To get a preview about upcoming topics or what was posted in past, please have a look here: Content++.

Feynman_2

quantocracy-badge-130

One thought on “Server -III-

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s