// Associate the service stub with the input channel which will be then used to send // messages to the client(s). public void AttachInputChannel(IDuplexInputChannel inputChannel) { using (EneterTrace.Entering()) { myInputChannel = inputChannel; // Find events in the service interface and subscribe to them. EventInfo[] anEvents = typeof(TServiceInterface).GetEvents(); foreach (EventInfo anEventInfo in anEvents) { Type anEventArgsType = anEventInfo.EventHandlerType.IsGenericType ? anEventInfo.EventHandlerType.GetGenericArguments()[0] : typeof(EventArgs); // This handler will be subscribed to events from the service. // Note: for each loop create a new local variable so that the context is preserved for the Action<,> event handler. // if anEventInfo is used then the reference would be changed. EventInfo aTmpEventInfo = anEventInfo; Action <object, EventArgs> anEventHandler = (sender, e) => { using (EneterTrace.Entering()) { string[] aSubscribedClients = null; using (ThreadLock.Lock(myServiceEvents)) { EventContext anEventContextTmp = myServiceEvents.FirstOrDefault(x => x.EventInfo.Name == aTmpEventInfo.Name); if (anEventContextTmp != null) { aSubscribedClients = anEventContextTmp.SubscribedClients.ToArray(); } } // If some client is subscribed. if (aSubscribedClients != null && aSubscribedClients.Length > 0) { object aSerializedEvent = null; // If there is one serializer for all clients then pre-serialize the message to increase the performance. if (mySerializer.IsSameForAllResponseReceivers()) { try { // Serialize the event and send it to subscribed clients. RpcMessage anEventMessage = new RpcMessage() { Id = 0, // dummy - because we do not need to track it. Request = ERpcRequest.RaiseEvent, OperationName = aTmpEventInfo.Name, SerializedParams = (anEventArgsType == typeof(EventArgs)) ? null : // EventArgs is a known type without parameters - we do not need to serialize it. new object[] { mySerializer.Serialize(anEventArgsType, e) } }; aSerializedEvent = mySerializer.Serialize <RpcMessage>(anEventMessage); } catch (Exception err) { EneterTrace.Error(TracedObject + "failed to serialize the event '" + aTmpEventInfo.Name + "'.", err); // Note: this exception will be thrown to the delegate that raised the event. throw; } } // Iterate via subscribed clients and send them the event. foreach (string aClient in aSubscribedClients) { try { // If there is serializer per client then serialize the message for each client. if (!mySerializer.IsSameForAllResponseReceivers()) { ISerializer aSerializer = mySerializer.ForResponseReceiver(aClient); RpcMessage anEventMessage = new RpcMessage() { Id = 0, // dummy - because we do not need to track it. Request = ERpcRequest.RaiseEvent, OperationName = aTmpEventInfo.Name, SerializedParams = (anEventArgsType == typeof(EventArgs)) ? null : // EventArgs is a known type without parameters - we do not need to serialize it. new object[] { aSerializer.Serialize(anEventArgsType, e) } }; // Note: do not store serialized data to aSerializedEvent because // if SendResponseMessage works asynchronously then the reference to serialized // data could be overridden. object aSerializedEventForClient = aSerializer.Serialize <RpcMessage>(anEventMessage); myInputChannel.SendResponseMessage(aClient, aSerializedEventForClient); } else { myInputChannel.SendResponseMessage(aClient, aSerializedEvent); } } catch (Exception err) { EneterTrace.Error(TracedObject + "failed to send event '" + aTmpEventInfo.Name + "' to the client.", err); // Suppose the client is disconnected so unsubscribe it from all events. UnsubscribeClientFromEvents(aClient); // Note: do not retrow the exception because other subscribed clients would not be notified. // E.g. if the exception occured because the client disconnected other clients should // not be affected. } } } } }; EventContext anEventContext = null; try { anEventContext = new EventContext(myService, anEventInfo, Delegate.CreateDelegate(anEventInfo.EventHandlerType, anEventHandler.Target, anEventHandler.Method)); anEventContext.Subscribe(); } catch (Exception err) { string anErrorMessage = TracedObject + "failed to attach the output channel because it failed to create EventContext."; EneterTrace.Error(anErrorMessage, err); throw; } using (ThreadLock.Lock(myServiceEvents)) { if (!myServiceEvents.Add(anEventContext)) { string anErrorMessage = TracedObject + "failed to attach the output channel because it failed to create the event '" + anEventInfo.Name + "' because the event already exists."; EneterTrace.Error(anErrorMessage); throw new InvalidOperationException(anErrorMessage); } } } } }
// This method is called when a message from the service is received. // This can be either the response for a request or it can be an event raised in the service. protected override void OnResponseMessageReceived(object sender, DuplexChannelMessageEventArgs e) { using (EneterTrace.Entering()) { RpcMessage aMessage = null; try { aMessage = mySerializer.ForResponseReceiver(e.ResponseReceiverId).Deserialize <RpcMessage>(e.Message); } catch (Exception err) { EneterTrace.Error(TracedObject + "failed to deserialize incoming message.", err); return; } // If it is a response for a call. if (aMessage.Request == ERpcRequest.Response) { EneterTrace.Debug("RETURN FROM RPC RECEIVED"); // Try to find if there is a pending request waiting for the response. RemoteCallContext anRpcContext; using (ThreadLock.Lock(myPendingRemoteCalls)) { myPendingRemoteCalls.TryGetValue(aMessage.Id, out anRpcContext); } if (anRpcContext != null) { if (string.IsNullOrEmpty(aMessage.ErrorType)) { anRpcContext.SerializedReturnValue = aMessage.SerializedReturn; } else { RpcException anException = new RpcException(aMessage.ErrorMessage, aMessage.ErrorType, aMessage.ErrorDetails); anRpcContext.Error = anException; } // Release the pending request. anRpcContext.RpcCompleted.Set(); } } else if (aMessage.Request == ERpcRequest.RaiseEvent) { EneterTrace.Debug("EVENT FROM SERVICE RECEIVED"); if (aMessage.SerializedParams != null && aMessage.SerializedParams.Length > 0) { // Try to raise an event. // The event is raised in its own thread so that the receiving thread is not blocked. // Note: raising an event cannot block handling of response messages because it can block // processing of an RPC response for which the RPC caller thread is waiting. // And if this waiting caller thread is a thread where events are routed and if the routing // of these events is 'blocking' then a deadlock can occur. // Therefore ThreadPool is used. EneterThreadPool.QueueUserWorkItem(() => myThreadDispatcher.Invoke(() => RaiseEvent(aMessage.OperationName, aMessage.SerializedParams[0]))); } else { // Note: this happens if the event is of type EventErgs. // The event is raised in its own thread so that the receiving thread is not blocked. EneterThreadPool.QueueUserWorkItem(() => myThreadDispatcher.Invoke(() => RaiseEvent(aMessage.OperationName, null))); } } else { EneterTrace.Warning(TracedObject + "detected a message with unknown flag number."); } } }
public void ProcessRemoteRequest(DuplexChannelMessageEventArgs e) { using (EneterTrace.Entering()) { ISerializer aSerializer = mySerializer.ForResponseReceiver(e.ResponseReceiverId); // Deserialize the incoming message. RpcMessage aRequestMessage = null; try { aRequestMessage = aSerializer.Deserialize <RpcMessage>(e.Message); } catch (Exception err) { EneterTrace.Error(TracedObject + "failed to deserialize incoming request message.", err); return; } RpcMessage aResponseMessage = new RpcMessage() { Id = aRequestMessage.Id, Request = ERpcRequest.Response }; // If it is a remote call of a method/function. if (aRequestMessage.Request == ERpcRequest.InvokeMethod) { EneterTrace.Debug("RPC RECEIVED"); // Get the method from the service that shall be invoked. ServiceMethod aServiceMethod; myServiceMethods.TryGetValue(aRequestMessage.OperationName, out aServiceMethod); if (aServiceMethod != null) { if (aRequestMessage.SerializedParams != null && aRequestMessage.SerializedParams.Length == aServiceMethod.InputParameterTypes.Length) { // Deserialize input parameters. object[] aDeserializedInputParameters = new object[aServiceMethod.InputParameterTypes.Length]; try { for (int i = 0; i < aServiceMethod.InputParameterTypes.Length; ++i) { aDeserializedInputParameters[i] = aSerializer.Deserialize(aServiceMethod.InputParameterTypes[i], aRequestMessage.SerializedParams[i]); } } catch (Exception err) { string anErrorMessage = "failed to deserialize input parameters for '" + aRequestMessage.OperationName + "'."; EneterTrace.Error(anErrorMessage, err); aResponseMessage.ErrorType = err.GetType().Name; aResponseMessage.ErrorMessage = anErrorMessage; aResponseMessage.ErrorDetails = err.ToString(); } if (string.IsNullOrEmpty(aResponseMessage.ErrorType)) { object aResult = null; try { // Invoke the service method. aResult = aServiceMethod.Method.Invoke(myService, aDeserializedInputParameters); } catch (Exception err) { // Note: Use InnerException to skip the wrapping ReflexionException. Exception ex = (err.InnerException != null) ? err.InnerException : err; EneterTrace.Error(TracedObject + ErrorHandler.DetectedException, ex); // The exception will be responded to the client. aResponseMessage.ErrorType = ex.GetType().Name; aResponseMessage.ErrorMessage = ex.Message; aResponseMessage.ErrorDetails = ex.ToString(); } if (string.IsNullOrEmpty(aResponseMessage.ErrorType)) { try { // Serialize the result. if (aServiceMethod.Method.ReturnType != typeof(void)) { aResponseMessage.SerializedReturn = aSerializer.Serialize(aServiceMethod.Method.ReturnType, aResult); } else { aResponseMessage.SerializedReturn = null; } } catch (Exception err) { string anErrorMessage = TracedObject + "failed to serialize the result."; EneterTrace.Error(anErrorMessage, err); aResponseMessage.ErrorType = err.GetType().Name; aResponseMessage.ErrorMessage = anErrorMessage; aResponseMessage.ErrorDetails = err.ToString(); } } } } else { aResponseMessage.ErrorType = typeof(InvalidOperationException).Name; aResponseMessage.ErrorMessage = TracedObject + "failed to process '" + aRequestMessage.OperationName + "' because it has incorrect number of input parameters."; EneterTrace.Error(aResponseMessage.ErrorMessage); } } else { aResponseMessage.ErrorType = typeof(InvalidOperationException).Name; aResponseMessage.ErrorMessage = "Method '" + aRequestMessage.OperationName + "' does not exist in the service."; EneterTrace.Error(aResponseMessage.ErrorMessage); } } // If it is a request to subscribe/unsubcribe an event. else if (aRequestMessage.Request == ERpcRequest.SubscribeEvent || aRequestMessage.Request == ERpcRequest.UnsubscribeEvent) { EventContext anEventContext = null; using (ThreadLock.Lock(myServiceEvents)) { anEventContext = myServiceEvents.FirstOrDefault(x => x.EventInfo.Name == aRequestMessage.OperationName); if (anEventContext != null) { if (aRequestMessage.Request == ERpcRequest.SubscribeEvent) { EneterTrace.Debug("SUBSCRIBE REMOTE EVENT RECEIVED"); // Note: Events are added to the HashSet. // Therefore it is ensured each client is subscribed only once. anEventContext.SubscribedClients.Add(e.ResponseReceiverId); } else { EneterTrace.Debug("UNSUBSCRIBE REMOTE EVENT RECEIVED"); anEventContext.SubscribedClients.Remove(e.ResponseReceiverId); } } } if (anEventContext == null) { aResponseMessage.ErrorType = typeof(InvalidOperationException).Name; aResponseMessage.ErrorMessage = TracedObject + "Event '" + aRequestMessage.OperationName + "' does not exist in the service."; EneterTrace.Error(aResponseMessage.ErrorMessage); } } else { aResponseMessage.ErrorType = typeof(InvalidOperationException).Name; aResponseMessage.ErrorMessage = TracedObject + "could not recognize the incoming request. If it is RPC, Subscribing or Unsubscribfing."; EneterTrace.Error(aResponseMessage.ErrorMessage); } try { // Serialize the response message. object aSerializedResponse = aSerializer.Serialize <RpcMessage>(aResponseMessage); myInputChannel.SendResponseMessage(e.ResponseReceiverId, aSerializedResponse); } catch (Exception err) { EneterTrace.Error(TracedObject + "." + aRequestMessage.OperationName + " " + ErrorHandler.FailedToSendResponseMessage, err); } } }
private object SerializeRpcMessage(RpcMessage rpcMessage) { using (MemoryStream aStream = new MemoryStream()) { BinaryWriter aWriter = new BinaryWriter(aStream); // Write Id of the request. myEncoderDecoder.WriteInt32(aWriter, rpcMessage.Id, myIsLittleEndian); // Write request flag. byte aRequestType = (byte)rpcMessage.Request; aWriter.Write(aRequestType); if (rpcMessage.Request == ERpcRequest.InvokeMethod || rpcMessage.Request == ERpcRequest.RaiseEvent) { // Write name of the method or name of the event which shall be raised. myEncoderDecoder.WritePlainString(aWriter, rpcMessage.OperationName, Encoding.UTF8, myIsLittleEndian); // Write number of input parameters. if (rpcMessage.SerializedParams == null) { myEncoderDecoder.WriteInt32(aWriter, 0, myIsLittleEndian); } else { myEncoderDecoder.WriteInt32(aWriter, rpcMessage.SerializedParams.Length, myIsLittleEndian); // Write already serialized input parameters. for (int i = 0; i < rpcMessage.SerializedParams.Length; ++i) { myEncoderDecoder.Write(aWriter, rpcMessage.SerializedParams[i], myIsLittleEndian); } } } else if (rpcMessage.Request == ERpcRequest.SubscribeEvent || rpcMessage.Request == ERpcRequest.UnsubscribeEvent) { // Write name of the event which shall be subscribed or unsubcribed. myEncoderDecoder.WritePlainString(aWriter, rpcMessage.OperationName, Encoding.UTF8, myIsLittleEndian); } else if (rpcMessage.Request == ERpcRequest.Response) { // Write already serialized return value. if (rpcMessage.SerializedReturn != null) { // Indicate it is not void. aWriter.Write((byte)1); // Wtrite return value. myEncoderDecoder.Write(aWriter, rpcMessage.SerializedReturn, myIsLittleEndian); } else { // Indicate there is no rturn value. aWriter.Write((byte)0); } if (!string.IsNullOrEmpty(rpcMessage.ErrorType)) { // Indicate the response contains the error message. aWriter.Write((byte)1); // Write error. myEncoderDecoder.WritePlainString(aWriter, rpcMessage.ErrorType, Encoding.UTF8, myIsLittleEndian); myEncoderDecoder.WritePlainString(aWriter, rpcMessage.ErrorMessage, Encoding.UTF8, myIsLittleEndian); myEncoderDecoder.WritePlainString(aWriter, rpcMessage.ErrorDetails, Encoding.UTF8, myIsLittleEndian); } else { // Indicate the response does not contain the error message. aWriter.Write((byte)0); } } return(aStream.ToArray()); } }