2 // ClientRuntimeChannel.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2006 Novell, Inc. http://www.novell.com
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections.Generic;
30 using System.Reflection;
31 using System.Runtime.Serialization;
32 using System.ServiceModel.Channels;
33 using System.ServiceModel.Description;
34 using System.ServiceModel.Dispatcher;
35 using System.ServiceModel.Security;
36 using System.Threading;
39 namespace System.ServiceModel.MonoInternal
41 #if DISABLE_REAL_PROXY
42 // FIXME: This is a quick workaround for bug #571907
45 interface IInternalContextChannel
47 ContractDescription Contract { get; }
49 object Process (MethodBase method, string operationName, object [] parameters, OperationContext context);
51 IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState);
53 object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result);
56 #if DISABLE_REAL_PROXY
57 // FIXME: This is a quick workaround for bug #571907
60 class ClientRuntimeChannel
61 : CommunicationObject, IClientChannel, IInternalContextChannel
63 ClientRuntime runtime;
64 EndpointAddress remote_address;
65 ContractDescription contract;
66 MessageVersion message_version;
67 TimeSpan default_open_timeout, default_close_timeout;
69 IChannelFactory factory;
70 TimeSpan? operation_timeout = null;
71 ChannelFactory channel_factory;
74 readonly ProcessDelegate _processDelegate;
76 delegate object ProcessDelegate (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context);
78 readonly RequestDelegate requestDelegate;
80 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
82 readonly SendDelegate sendDelegate;
84 delegate void SendDelegate (Message msg, TimeSpan timeout);
87 public ClientRuntimeChannel (ServiceEndpoint endpoint,
88 ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
89 : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
91 channel_factory = channelFactory;
94 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
97 throw new ArgumentNullException ("runtime");
98 if (messageVersion == null)
99 throw new ArgumentNullException ("messageVersion");
100 this.runtime = runtime;
101 this.remote_address = remoteAddress;
102 if (runtime.Via == null)
103 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
104 this.contract = contract;
105 this.message_version = messageVersion;
106 default_open_timeout = openTimeout;
107 default_close_timeout = closeTimeout;
108 _processDelegate = new ProcessDelegate (Process);
109 requestDelegate = new RequestDelegate (Request);
110 sendDelegate = new SendDelegate (Send);
113 AllowInitializationUI = true;
115 if (contextChannel != null)
116 channel = contextChannel;
118 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
120 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
121 this.factory = factory;
122 } catch (TargetInvocationException ex) {
123 if (ex.InnerException != null)
124 throw ex.InnerException;
131 public ContractDescription Contract {
132 get { return contract; }
135 public ClientRuntime Runtime {
136 get { return runtime; }
139 IRequestChannel RequestChannel {
140 get { return channel as IRequestChannel; }
143 IOutputChannel OutputChannel {
144 get { return channel as IOutputChannel; }
147 internal IDuplexChannel DuplexChannel {
148 get { return channel as IDuplexChannel; }
151 #region IClientChannel
153 bool did_interactive_initialization;
155 public bool AllowInitializationUI { get; set; }
157 public bool DidInteractiveInitialization {
158 get { return did_interactive_initialization; }
162 get { return runtime.Via; }
165 class DelegatingWaitHandle : WaitHandle
167 public DelegatingWaitHandle (IAsyncResult [] results)
169 this.results = results;
172 IAsyncResult [] results;
174 protected override void Dispose (bool disposing)
177 foreach (var r in results)
178 r.AsyncWaitHandle.Close ();
181 public override bool WaitOne ()
183 foreach (var r in results)
184 r.AsyncWaitHandle.WaitOne ();
188 public override bool WaitOne (int millisecondsTimeout)
190 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
193 WaitHandle [] ResultWaitHandles {
195 var arr = new WaitHandle [results.Length];
196 for (int i = 0; i < arr.Length; i++)
197 arr [i] = results [i].AsyncWaitHandle;
202 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
204 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
207 public override bool WaitOne (TimeSpan timeout, bool exitContext)
209 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
213 class DisplayUIAsyncResult : IAsyncResult
215 public DisplayUIAsyncResult (IAsyncResult [] results)
217 this.results = results;
220 IAsyncResult [] results;
222 internal IAsyncResult [] Results {
223 get { return results; }
226 public object AsyncState {
230 WaitHandle wait_handle;
232 public WaitHandle AsyncWaitHandle {
234 if (wait_handle == null)
235 wait_handle = new DelegatingWaitHandle (results);
240 public bool CompletedSynchronously {
242 foreach (var r in results)
243 if (!r.CompletedSynchronously)
248 public bool IsCompleted {
250 foreach (var r in results)
258 public IAsyncResult BeginDisplayInitializationUI (
259 AsyncCallback callback, object state)
261 OnInitializationUI ();
262 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
264 foreach (var init in runtime.InteractiveChannelInitializers)
265 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
266 return new DisplayUIAsyncResult (arr);
269 public void EndDisplayInitializationUI (
272 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
274 foreach (var init in runtime.InteractiveChannelInitializers)
275 init.EndDisplayInitializationUI (r.Results [i++]);
277 did_interactive_initialization = true;
280 public void DisplayInitializationUI ()
282 OnInitializationUI ();
283 foreach (var init in runtime.InteractiveChannelInitializers)
284 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
286 did_interactive_initialization = true;
289 void OnInitializationUI ()
291 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
292 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
295 public void Dispose ()
300 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
304 #region IContextChannel
307 public bool AllowOutputBatching { get; set; }
309 public IInputSession InputSession {
311 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
312 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
315 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
316 return dch != null ? dch.Session : null;
320 public EndpointAddress LocalAddress {
322 var dc = OperationChannel as IDuplexChannel;
323 return dc != null ? dc.LocalAddress : null;
327 public TimeSpan OperationTimeout {
328 get { return this.operation_timeout ?? (channel_factory != null ? channel_factory.Endpoint.Binding.SendTimeout : DefaultCommunicationTimeouts.Instance.SendTimeout); }
329 set { this.operation_timeout = value; }
332 public IOutputSession OutputSession {
334 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
335 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
338 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
339 return dch != null ? dch.Session : null;
343 public EndpointAddress RemoteAddress {
344 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
347 public string SessionId {
348 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
353 // CommunicationObject
354 protected internal override TimeSpan DefaultOpenTimeout {
355 get { return default_open_timeout; }
358 protected internal override TimeSpan DefaultCloseTimeout {
359 get { return default_close_timeout; }
362 protected override void OnAbort ()
365 if (factory != null) // ... is it valid?
369 Action<TimeSpan> close_delegate;
371 protected override IAsyncResult OnBeginClose (
372 TimeSpan timeout, AsyncCallback callback, object state)
374 if (close_delegate == null)
375 close_delegate = new Action<TimeSpan> (OnClose);
376 return close_delegate.BeginInvoke (timeout, callback, state);
379 protected override void OnEndClose (IAsyncResult result)
381 close_delegate.EndInvoke (result);
384 protected override void OnClose (TimeSpan timeout)
386 DateTime start = DateTime.Now;
387 if (channel.State == CommunicationState.Opened)
388 channel.Close (timeout);
391 Action<TimeSpan> open_callback;
393 protected override IAsyncResult OnBeginOpen (
394 TimeSpan timeout, AsyncCallback callback, object state)
396 if (open_callback == null)
397 open_callback = new Action<TimeSpan> (OnOpen);
398 return open_callback.BeginInvoke (timeout, callback, state);
401 protected override void OnEndOpen (IAsyncResult result)
403 if (open_callback == null)
404 throw new InvalidOperationException ("Async open operation has not started");
405 open_callback.EndInvoke (result);
408 protected override void OnOpen (TimeSpan timeout)
410 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
411 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
412 if (channel.State == CommunicationState.Created)
413 channel.Open (timeout);
418 IChannel OperationChannel {
419 get { return channel; }
422 public T GetProperty<T> () where T : class
424 if (typeof (T) == typeof (MessageVersion))
425 return (T) (object) message_version;
426 return OperationChannel.GetProperty<T> ();
429 // IExtensibleObject<IContextChannel>
431 IExtensionCollection<IContextChannel> extensions;
433 public IExtensionCollection<IContextChannel> Extensions {
435 if (extensions == null)
436 extensions = new ExtensionCollection<IContextChannel> (this);
441 #region Request/Output processing
443 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
446 var retval = _processDelegate.BeginInvoke (method, operationName, true, ref p, OperationContext.Current, callback, asyncState);
448 throw new InvalidOperationException ();
452 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
455 throw new ArgumentNullException ("result");
456 if (parameters == null)
457 throw new ArgumentNullException ("parameters");
459 object[] p = parameters;
460 var retval = _processDelegate.EndInvoke (ref p, result);
464 Array.Copy (p, parameters, p.Length);
468 public object Process (MethodBase method, string operationName, object [] parameters, OperationContext context)
471 var retval = Process (method, operationName, false, ref p, context);
473 throw new InvalidOperationException ();
477 object Process (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context)
479 var previousContext = OperationContext.Current;
481 // Inherit the context from the calling thread
482 OperationContext.Current = context;
484 return DoProcess (method, operationName, isAsync, ref parameters, context);
485 } catch (Exception ex) {
488 // Reset the context before the thread goes back into the pool
489 OperationContext.Current = previousContext;
493 object DoProcess (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context)
495 if (AllowInitializationUI)
496 DisplayInitializationUI ();
497 OperationDescription od = SelectOperation (method, operationName, parameters);
499 if (State != CommunicationState.Opened)
503 return Request (od, isAsync, ref parameters, context);
505 Output (od, parameters, context);
510 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
513 if (Runtime.OperationSelector != null)
514 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
516 operation = operationName;
517 OperationDescription od = contract.Operations.Find (operation);
519 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
523 void Output (OperationDescription od, object [] parameters, OperationContext context)
525 ClientOperation op = runtime.Operations [od.Name];
526 Send (CreateRequest (op, parameters, context), OperationTimeout);
529 object Request (OperationDescription od, bool isAsync, ref object [] parameters, OperationContext context)
531 ClientOperation op = runtime.Operations [od.Name];
532 object [] inspections = new object [runtime.MessageInspectors.Count];
533 Message req = CreateRequest (op, parameters, context);
535 for (int i = 0; i < inspections.Length; i++)
536 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
538 Message res = Request (req, OperationTimeout);
540 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
541 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
542 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
544 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
545 if (fault.HasDetail) {
546 Type detailType = typeof (ExceptionDetail);
547 var freader = fault.GetReaderAtDetailContents ();
548 DataContractSerializer ds = null;
549 foreach (var fci in op.FaultContractInfos)
550 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
551 detailType = fci.Detail;
556 ds = new DataContractSerializer (detailType);
557 var detail = ds.ReadObject (freader);
558 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
562 ex = new FaultException (fault);
567 for (int i = 0; i < inspections.Length; i++)
568 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
570 if (!op.DeserializeReply)
573 if (isAsync && od.EndMethod != null) {
574 var endParams = od.EndMethod.GetParameters ();
575 parameters = new object [endParams.Length - 1];
578 return op.Formatter.DeserializeReply (res, parameters);
581 #region Message-based Request() and Send()
582 // They are internal for ClientBase<T>.ChannelBase use.
583 internal Message Request (Message msg, TimeSpan timeout)
585 if (RequestChannel != null)
586 return RequestChannel.Request (msg, timeout);
588 return RequestCorrelated (msg, timeout, OutputChannel);
591 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
593 // FIXME: implement ConcurrencyMode check:
594 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
596 DateTime startTime = DateTime.Now;
597 OutputChannel.Send (msg, timeout);
598 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
601 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
603 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
606 internal Message EndRequest (IAsyncResult result)
608 return requestDelegate.EndInvoke (result);
611 internal void Send (Message msg, TimeSpan timeout)
613 if (OutputChannel != null)
614 OutputChannel.Send (msg, timeout);
616 RequestChannel.Request (msg, timeout); // and ignore returned message.
619 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
621 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
624 internal void EndSend (IAsyncResult result)
626 sendDelegate.EndInvoke (result);
630 Message CreateRequest (ClientOperation op, object [] parameters, OperationContext context)
632 MessageVersion version = message_version;
634 version = MessageVersion.Default;
637 if (op.SerializeRequest)
638 msg = op.Formatter.SerializeRequest (
639 version, parameters);
641 if (parameters.Length != 1)
642 throw new ArgumentException (String.Format ("Argument parameters does not match the expected input. It should contain only a Message, but has {0} parameters", parameters.Length));
643 if (!(parameters [0] is Message))
644 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
645 msg = (Message) parameters [0];
648 context = context ?? OperationContext.Current;
649 if (context != null) {
650 // CopyHeadersFrom does not work here (brings duplicates -> error)
651 foreach (var mh in context.OutgoingMessageHeaders) {
652 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
654 msg.Headers.RemoveAt (x);
655 msg.Headers.Add ((MessageHeader) mh);
657 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
660 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
661 //if (OutputSession != null)
662 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
663 msg.Properties.AllowOutputBatching = AllowOutputBatching;
665 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
666 if (msg.Headers.MessageId == null)
667 msg.Headers.MessageId = new UniqueId ();
668 if (msg.Headers.ReplyTo == null)
669 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
670 if (msg.Headers.To == null && RemoteAddress != null)
671 msg.Headers.To = RemoteAddress.Uri;