//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------- namespace System.ServiceModel.Routing { using System; using System.Collections.Generic; using System.Runtime; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.Transactions; using System.Runtime.Diagnostics; using System.ServiceModel.Diagnostics; static class ClientFactory { public static IRoutingClient Create(RoutingEndpointTrait endpointTrait, RoutingService service, bool impersonating) { Type contractType = endpointTrait.RouterContract; IRoutingClient client; if (contractType == typeof(ISimplexDatagramRouter)) { client = new SimplexDatagramClient(endpointTrait, service.RoutingConfig, impersonating); } else if (contractType == typeof(IRequestReplyRouter)) { client = new RequestReplyClient(endpointTrait, service.RoutingConfig, impersonating); } else if (contractType == typeof(ISimplexSessionRouter)) { client = new SimplexSessionClient(endpointTrait, service.RoutingConfig, impersonating); } else //if (contractType == typeof(IDuplexSessionRouter)) { Fx.Assert(contractType == typeof(IDuplexSessionRouter), "Only one contract type remaining."); client = new DuplexSessionClient(service, endpointTrait, impersonating); } return client; } abstract class RoutingClientBase : ClientBase, IRoutingClient where TChannel : class { bool openCompleted; object thisLock; Queue waiters; protected RoutingClientBase(RoutingEndpointTrait endpointTrait, RoutingConfiguration routingConfig, bool impersonating) : base(endpointTrait.Endpoint.Binding, endpointTrait.Endpoint.Address) { Initialize(endpointTrait, routingConfig, impersonating); } protected RoutingClientBase(RoutingEndpointTrait endpointTrait, RoutingConfiguration routingConfig, object callbackInstance, bool impersonating) : base(new InstanceContext(callbackInstance), endpointTrait.Endpoint.Binding, endpointTrait.Endpoint.Address) { Initialize(endpointTrait, routingConfig, impersonating); } public RoutingEndpointTrait Key { get; private set; } public event EventHandler Faulted; static void ConfigureImpersonation(ServiceEndpoint endpoint, bool impersonating) { // Used for both impersonation and ASP.NET Compatibilty Mode. Both currently require // everything to be synchronous. if (impersonating) { CustomBinding binding = endpoint.Binding as CustomBinding; if (binding == null) { binding = new CustomBinding(endpoint.Binding); } SynchronousSendBindingElement syncSend = binding.Elements.Find(); if (syncSend == null) { binding.Elements.Insert(0, new SynchronousSendBindingElement()); endpoint.Binding = binding; } } } static void ConfigureTransactionFlow(ServiceEndpoint endpoint) { CustomBinding binding = endpoint.Binding as CustomBinding; if (binding == null) { binding = new CustomBinding(endpoint.Binding); } TransactionFlowBindingElement transactionFlow = binding.Elements.Find(); if (transactionFlow != null) { transactionFlow.AllowWildcardAction = true; endpoint.Binding = binding; } } void Initialize(RoutingEndpointTrait endpointTrait, RoutingConfiguration routingConfig, bool impersonating) { this.thisLock = new object(); this.Key = endpointTrait; if (TD.RoutingServiceCreatingClientForEndpointIsEnabled()) { TD.RoutingServiceCreatingClientForEndpoint(this.Key.ToString()); } ServiceEndpoint clientEndpoint = endpointTrait.Endpoint; ServiceEndpoint endpoint = this.Endpoint; KeyedByTypeCollection behaviors = endpoint.Behaviors; endpoint.ListenUri = clientEndpoint.ListenUri; endpoint.ListenUriMode = clientEndpoint.ListenUriMode; endpoint.Name = clientEndpoint.Name; foreach (IEndpointBehavior behavior in clientEndpoint.Behaviors) { // Remove if present, ok to call if not there (will simply return false) behaviors.Remove(behavior.GetType()); behaviors.Add(behavior); } // If the configuration doesn't explicitly add MustUnderstandBehavior (to override us) // add it here, with mustunderstand = false. if (behaviors.Find() == null) { behaviors.Add(new MustUnderstandBehavior(false)); } // If the configuration doesn't explicitly turn off marshaling we add it here. if (routingConfig.SoapProcessingEnabled && behaviors.Find() == null) { behaviors.Add(new SoapProcessingBehavior()); } ConfigureTransactionFlow(endpoint); ConfigureImpersonation(endpoint, impersonating); } protected override TChannel CreateChannel() { TChannel channel = base.CreateChannel(); ((ICommunicationObject)channel).Faulted += this.InnerChannelFaulted; return channel; } public IAsyncResult BeginOperation(Message message, Transaction transaction, AsyncCallback callback, object state) { return new OperationAsyncResult(this, message, transaction, callback, state); } public Message EndOperation(IAsyncResult result) { return OperationAsyncResult.End(result); } protected abstract IAsyncResult OnBeginOperation(Message message, AsyncCallback callback, object state); protected abstract Message OnEndOperation(IAsyncResult asyncResult); void InnerChannelFaulted(object sender, EventArgs args) { EventHandler handlers = this.Faulted; if (handlers != null) { handlers(this, args); } } class OperationAsyncResult : TransactedAsyncResult { static AsyncCompletion openComplete = OpenComplete; static AsyncCompletion operationComplete = OperationComplete; static Action signalWaiter; RoutingClientBase parent; Message replyMessage; Message requestMessage; Transaction transaction; public OperationAsyncResult(RoutingClientBase parent, Message requestMessage, Transaction transaction, AsyncCallback callback, object state) : base(callback, state) { this.parent = parent; this.requestMessage = requestMessage; this.transaction = transaction; bool shouldOpen = false; if (!this.parent.openCompleted) { lock (this.parent.thisLock) { if (!this.parent.openCompleted) { //The first to open initializes the waiters queue others add themselves to it. if (this.parent.waiters == null) { //it's our job to open the proxy this.parent.waiters = new Queue(); shouldOpen = true; } else { //Someone beat us to it, just join the list of waiters. this.parent.waiters.Enqueue(this); return; } } } } if (shouldOpen) { //we are the first so we need to open this channel IAsyncResult asyncResult; using (this.PrepareTransactionalCall(this.transaction)) { //This will use the binding's OpenTimeout. asyncResult = ((ICommunicationObject)this.parent).BeginOpen(this.PrepareAsyncCompletion(openComplete), this); } if (this.SyncContinue(asyncResult)) { this.Complete(true); } } else { if (this.CallOperation()) { this.Complete(true); } } } public static Message End(IAsyncResult result) { OperationAsyncResult thisPtr = AsyncResult.End(result); return thisPtr.replyMessage; } static bool OpenComplete(IAsyncResult openResult) { OperationAsyncResult thisPtr = (OperationAsyncResult)openResult.AsyncState; try { ((ICommunicationObject)thisPtr.parent).EndOpen(openResult); } finally { Queue localWaiters = null; lock (thisPtr.parent.thisLock) { localWaiters = thisPtr.parent.waiters; thisPtr.parent.waiters = null; thisPtr.parent.openCompleted = true; } if (localWaiters != null && localWaiters.Count > 0) { if (signalWaiter == null) { signalWaiter = new Action(SignalWaiter); } //foreach over Queue goes FIFO foreach (OperationAsyncResult waiter in localWaiters) { ActionItem.Schedule(signalWaiter, waiter); } } } return thisPtr.CallOperation(); } bool CallOperation() { IAsyncResult asyncResult; using (this.PrepareTransactionalCall(this.transaction)) { asyncResult = this.parent.OnBeginOperation(this.requestMessage, this.PrepareAsyncCompletion(operationComplete), this); } return this.SyncContinue(asyncResult); } static bool OperationComplete(IAsyncResult result) { OperationAsyncResult thisPtr = (OperationAsyncResult)result.AsyncState; thisPtr.replyMessage = thisPtr.parent.OnEndOperation(result); return true; } static void SignalWaiter(object state) { OperationAsyncResult waiter = (OperationAsyncResult)state; try { if (waiter.CallOperation()) { waiter.Complete(false); } } catch (Exception exception) { if (Fx.IsFatal(exception)) { throw; } waiter.Complete(false, exception); } } } } class SimplexDatagramClient : RoutingClientBase { public SimplexDatagramClient(RoutingEndpointTrait endpointTrait, RoutingConfiguration routingConfig, bool impersonating) : base(endpointTrait, routingConfig, impersonating) { } protected override IAsyncResult OnBeginOperation(Message message, AsyncCallback callback, object state) { return this.Channel.BeginProcessMessage(message, callback, state); } protected override Message OnEndOperation(IAsyncResult result) { this.Channel.EndProcessMessage(result); return null; } } class SimplexSessionClient : RoutingClientBase { public SimplexSessionClient(RoutingEndpointTrait endointTrait, RoutingConfiguration routingConfig, bool impersonating) : base(endointTrait, routingConfig, impersonating) { } protected override IAsyncResult OnBeginOperation(Message message, AsyncCallback callback, object state) { return this.Channel.BeginProcessMessage(message, callback, state); } protected override Message OnEndOperation(IAsyncResult result) { this.Channel.EndProcessMessage(result); return null; } } class DuplexSessionClient : RoutingClientBase { public DuplexSessionClient(RoutingService service, RoutingEndpointTrait endpointTrait, bool impersonating) : base(endpointTrait, service.RoutingConfig, new DuplexCallbackProxy(service.ChannelExtension.ActivityID, endpointTrait.CallbackInstance), impersonating) { } protected override IAsyncResult OnBeginOperation(Message message, AsyncCallback callback, object state) { return this.Channel.BeginProcessMessage(message, callback, state); } protected override Message OnEndOperation(IAsyncResult result) { this.Channel.EndProcessMessage(result); return null; } class DuplexCallbackProxy : IDuplexRouterCallback { Guid activityID; IDuplexRouterCallback callbackInstance; EventTraceActivity eventTraceActivity; public DuplexCallbackProxy(Guid activityID, IDuplexRouterCallback callbackInstance) { this.activityID = activityID; this.callbackInstance = callbackInstance; if (Fx.Trace.IsEtwProviderEnabled) { this.eventTraceActivity = new EventTraceActivity(activityID); } } IAsyncResult IDuplexRouterCallback.BeginProcessMessage(Message message, AsyncCallback callback, object state) { FxTrace.Trace.SetAndTraceTransfer(this.activityID, true); try { return new CallbackAsyncResult(this.callbackInstance, message, callback, state); } catch (Exception e) { if (TD.RoutingServiceDuplexCallbackExceptionIsEnabled()) { TD.RoutingServiceDuplexCallbackException(this.eventTraceActivity, "DuplexCallbackProxy.BeginProcessMessage", e); } throw; } } void IDuplexRouterCallback.EndProcessMessage(IAsyncResult result) { FxTrace.Trace.SetAndTraceTransfer(this.activityID, true); try { CallbackAsyncResult.End(result); } catch (Exception e) { if (TD.RoutingServiceDuplexCallbackExceptionIsEnabled()) { TD.RoutingServiceDuplexCallbackException(this.eventTraceActivity, "DuplexCallbackProxy.EndProcessMessage", e); } throw; } } // We have to have an AsyncResult implementation here in order to handle the // TransactionScope appropriately (use PrepareTransactionalCall, SyncContinue, etc...) class CallbackAsyncResult : TransactedAsyncResult { static AsyncCompletion processCallback = ProcessCallback; IDuplexRouterCallback callbackInstance; public CallbackAsyncResult(IDuplexRouterCallback callbackInstance, Message message, AsyncCallback callback, object state) : base(callback, state) { this.callbackInstance = callbackInstance; IAsyncResult result; using (this.PrepareTransactionalCall(TransactionMessageProperty.TryGetTransaction(message))) { result = this.callbackInstance.BeginProcessMessage(message, this.PrepareAsyncCompletion(processCallback), this); } if (this.SyncContinue(result)) { this.Complete(true); } } public static void End(IAsyncResult result) { AsyncResult.End(result); } static bool ProcessCallback(IAsyncResult result) { CallbackAsyncResult thisPtr = (CallbackAsyncResult)result.AsyncState; thisPtr.callbackInstance.EndProcessMessage(result); return true; } } } } class RequestReplyClient : RoutingClientBase { public RequestReplyClient(RoutingEndpointTrait endpointTrait, RoutingConfiguration routingConfig, bool impersonating) : base(endpointTrait, routingConfig, impersonating) { } protected override IAsyncResult OnBeginOperation(Message message, AsyncCallback callback, object state) { return this.Channel.BeginProcessRequest(message, callback, state); } protected override Message OnEndOperation(IAsyncResult result) { return this.Channel.EndProcessRequest(result); } } } }