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;
72 readonly ProcessDelegate _processDelegate;
74 delegate object ProcessDelegate (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context);
76 readonly RequestDelegate requestDelegate;
78 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
80 readonly SendDelegate sendDelegate;
82 delegate void SendDelegate (Message msg, TimeSpan timeout);
85 public ClientRuntimeChannel (ServiceEndpoint endpoint,
86 ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
87 : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
91 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
94 throw new ArgumentNullException ("runtime");
95 if (messageVersion == null)
96 throw new ArgumentNullException ("messageVersion");
97 this.runtime = runtime;
98 this.remote_address = remoteAddress;
99 if (runtime.Via == null)
100 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
101 this.contract = contract;
102 this.message_version = messageVersion;
103 default_open_timeout = openTimeout;
104 default_close_timeout = closeTimeout;
105 _processDelegate = new ProcessDelegate (Process);
106 requestDelegate = new RequestDelegate (Request);
107 sendDelegate = new SendDelegate (Send);
110 AllowInitializationUI = true;
111 OperationTimeout = TimeSpan.FromMinutes (1);
113 if (contextChannel != null)
114 channel = contextChannel;
116 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
118 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
119 this.factory = factory;
120 } catch (TargetInvocationException ex) {
121 if (ex.InnerException != null)
122 throw ex.InnerException;
129 public ContractDescription Contract {
130 get { return contract; }
133 public ClientRuntime Runtime {
134 get { return runtime; }
137 IRequestChannel RequestChannel {
138 get { return channel as IRequestChannel; }
141 IOutputChannel OutputChannel {
142 get { return channel as IOutputChannel; }
145 internal IDuplexChannel DuplexChannel {
146 get { return channel as IDuplexChannel; }
149 #region IClientChannel
151 bool did_interactive_initialization;
153 public bool AllowInitializationUI { get; set; }
155 public bool DidInteractiveInitialization {
156 get { return did_interactive_initialization; }
160 get { return runtime.Via; }
163 class DelegatingWaitHandle : WaitHandle
165 public DelegatingWaitHandle (IAsyncResult [] results)
167 this.results = results;
170 IAsyncResult [] results;
172 protected override void Dispose (bool disposing)
175 foreach (var r in results)
176 r.AsyncWaitHandle.Close ();
179 public override bool WaitOne ()
181 foreach (var r in results)
182 r.AsyncWaitHandle.WaitOne ();
186 public override bool WaitOne (int millisecondsTimeout)
188 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
191 WaitHandle [] ResultWaitHandles {
193 var arr = new WaitHandle [results.Length];
194 for (int i = 0; i < arr.Length; i++)
195 arr [i] = results [i].AsyncWaitHandle;
200 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
202 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
205 public override bool WaitOne (TimeSpan timeout, bool exitContext)
207 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
211 class DisplayUIAsyncResult : IAsyncResult
213 public DisplayUIAsyncResult (IAsyncResult [] results)
215 this.results = results;
218 IAsyncResult [] results;
220 internal IAsyncResult [] Results {
221 get { return results; }
224 public object AsyncState {
228 WaitHandle wait_handle;
230 public WaitHandle AsyncWaitHandle {
232 if (wait_handle == null)
233 wait_handle = new DelegatingWaitHandle (results);
238 public bool CompletedSynchronously {
240 foreach (var r in results)
241 if (!r.CompletedSynchronously)
246 public bool IsCompleted {
248 foreach (var r in results)
256 public IAsyncResult BeginDisplayInitializationUI (
257 AsyncCallback callback, object state)
259 OnInitializationUI ();
260 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
262 foreach (var init in runtime.InteractiveChannelInitializers)
263 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
264 return new DisplayUIAsyncResult (arr);
267 public void EndDisplayInitializationUI (
270 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
272 foreach (var init in runtime.InteractiveChannelInitializers)
273 init.EndDisplayInitializationUI (r.Results [i++]);
275 did_interactive_initialization = true;
278 public void DisplayInitializationUI ()
280 OnInitializationUI ();
281 foreach (var init in runtime.InteractiveChannelInitializers)
282 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
284 did_interactive_initialization = true;
287 void OnInitializationUI ()
289 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
290 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
293 public void Dispose ()
298 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
302 #region IContextChannel
305 public bool AllowOutputBatching { get; set; }
307 public IInputSession InputSession {
309 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
310 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
313 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
314 return dch != null ? dch.Session : null;
318 public EndpointAddress LocalAddress {
320 var dc = OperationChannel as IDuplexChannel;
321 return dc != null ? dc.LocalAddress : null;
326 public TimeSpan OperationTimeout { get; set; }
328 public IOutputSession OutputSession {
330 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
331 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
334 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
335 return dch != null ? dch.Session : null;
339 public EndpointAddress RemoteAddress {
340 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
343 public string SessionId {
344 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
349 // CommunicationObject
350 protected internal override TimeSpan DefaultOpenTimeout {
351 get { return default_open_timeout; }
354 protected internal override TimeSpan DefaultCloseTimeout {
355 get { return default_close_timeout; }
358 protected override void OnAbort ()
361 if (factory != null) // ... is it valid?
365 Action<TimeSpan> close_delegate;
367 protected override IAsyncResult OnBeginClose (
368 TimeSpan timeout, AsyncCallback callback, object state)
370 if (close_delegate == null)
371 close_delegate = new Action<TimeSpan> (OnClose);
372 return close_delegate.BeginInvoke (timeout, callback, state);
375 protected override void OnEndClose (IAsyncResult result)
377 close_delegate.EndInvoke (result);
380 protected override void OnClose (TimeSpan timeout)
382 DateTime start = DateTime.Now;
383 if (channel.State == CommunicationState.Opened)
384 channel.Close (timeout);
387 Action<TimeSpan> open_callback;
389 protected override IAsyncResult OnBeginOpen (
390 TimeSpan timeout, AsyncCallback callback, object state)
392 if (open_callback == null)
393 open_callback = new Action<TimeSpan> (OnOpen);
394 return open_callback.BeginInvoke (timeout, callback, state);
397 protected override void OnEndOpen (IAsyncResult result)
399 if (open_callback == null)
400 throw new InvalidOperationException ("Async open operation has not started");
401 open_callback.EndInvoke (result);
404 protected override void OnOpen (TimeSpan timeout)
406 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
407 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
408 if (channel.State == CommunicationState.Created)
409 channel.Open (timeout);
414 IChannel OperationChannel {
415 get { return channel; }
418 public T GetProperty<T> () where T : class
420 if (typeof (T) == typeof (MessageVersion))
421 return (T) (object) message_version;
422 return OperationChannel.GetProperty<T> ();
425 // IExtensibleObject<IContextChannel>
427 IExtensionCollection<IContextChannel> extensions;
429 public IExtensionCollection<IContextChannel> Extensions {
431 if (extensions == null)
432 extensions = new ExtensionCollection<IContextChannel> (this);
437 #region Request/Output processing
439 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
442 var retval = _processDelegate.BeginInvoke (method, operationName, true, ref p, OperationContext.Current, callback, asyncState);
444 throw new InvalidOperationException ();
448 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
451 throw new ArgumentNullException ("result");
452 if (parameters == null)
453 throw new ArgumentNullException ("parameters");
455 object[] p = parameters;
456 var retval = _processDelegate.EndInvoke (ref p, result);
460 if (p.Length != parameters.Length)
461 throw new InvalidOperationException ();
462 Array.Copy (p, parameters, p.Length);
466 public object Process (MethodBase method, string operationName, object [] parameters, OperationContext context)
469 var retval = Process (method, operationName, false, ref p, context);
471 throw new InvalidOperationException ();
475 object Process (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context)
477 var previousContext = OperationContext.Current;
479 // Inherit the context from the calling thread
480 OperationContext.Current = context;
482 return DoProcess (method, operationName, isAsync, ref parameters, context);
483 } catch (Exception ex) {
486 // Reset the context before the thread goes back into the pool
487 OperationContext.Current = previousContext;
491 object DoProcess (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context)
493 if (AllowInitializationUI)
494 DisplayInitializationUI ();
495 OperationDescription od = SelectOperation (method, operationName, parameters);
497 if (State != CommunicationState.Opened)
501 return Request (od, isAsync, ref parameters, context);
503 Output (od, parameters, context);
508 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
511 if (Runtime.OperationSelector != null)
512 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
514 operation = operationName;
515 OperationDescription od = contract.Operations.Find (operation);
517 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
521 void Output (OperationDescription od, object [] parameters, OperationContext context)
523 ClientOperation op = runtime.Operations [od.Name];
524 Send (CreateRequest (op, parameters, context), OperationTimeout);
527 object Request (OperationDescription od, bool isAsync, ref object [] parameters, OperationContext context)
529 ClientOperation op = runtime.Operations [od.Name];
530 object [] inspections = new object [runtime.MessageInspectors.Count];
531 Message req = CreateRequest (op, parameters, context);
533 for (int i = 0; i < inspections.Length; i++)
534 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
536 Message res = Request (req, OperationTimeout);
538 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
539 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
540 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
542 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
543 if (fault.HasDetail) {
544 Type detailType = typeof (ExceptionDetail);
545 var freader = fault.GetReaderAtDetailContents ();
546 DataContractSerializer ds = null;
547 foreach (var fci in op.FaultContractInfos)
548 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
549 detailType = fci.Detail;
554 ds = new DataContractSerializer (detailType);
555 var detail = ds.ReadObject (freader);
556 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
560 ex = new FaultException (fault);
565 for (int i = 0; i < inspections.Length; i++)
566 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
568 if (!op.DeserializeReply)
571 if (isAsync && od.EndMethod != null) {
572 var endParams = od.EndMethod.GetParameters ();
573 parameters = new object [endParams.Length - 1];
576 return op.Formatter.DeserializeReply (res, parameters);
579 #region Message-based Request() and Send()
580 // They are internal for ClientBase<T>.ChannelBase use.
581 internal Message Request (Message msg, TimeSpan timeout)
583 if (RequestChannel != null)
584 return RequestChannel.Request (msg, timeout);
586 return RequestCorrelated (msg, timeout, OutputChannel);
589 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
591 // FIXME: implement ConcurrencyMode check:
592 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
594 DateTime startTime = DateTime.Now;
595 OutputChannel.Send (msg, timeout);
596 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
599 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
601 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
604 internal Message EndRequest (IAsyncResult result)
606 return requestDelegate.EndInvoke (result);
609 internal void Send (Message msg, TimeSpan timeout)
611 if (OutputChannel != null)
612 OutputChannel.Send (msg, timeout);
614 RequestChannel.Request (msg, timeout); // and ignore returned message.
617 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
619 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
622 internal void EndSend (IAsyncResult result)
624 sendDelegate.EndInvoke (result);
628 Message CreateRequest (ClientOperation op, object [] parameters, OperationContext context)
630 MessageVersion version = message_version;
632 version = MessageVersion.Default;
635 if (op.SerializeRequest)
636 msg = op.Formatter.SerializeRequest (
637 version, parameters);
639 if (parameters.Length != 1)
640 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));
641 if (!(parameters [0] is Message))
642 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
643 msg = (Message) parameters [0];
646 context = context ?? OperationContext.Current;
647 if (context != null) {
648 // CopyHeadersFrom does not work here (brings duplicates -> error)
649 foreach (var mh in context.OutgoingMessageHeaders) {
650 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
652 msg.Headers.RemoveAt (x);
653 msg.Headers.Add ((MessageHeader) mh);
655 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
658 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
659 //if (OutputSession != null)
660 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
661 msg.Properties.AllowOutputBatching = AllowOutputBatching;
663 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
664 if (msg.Headers.MessageId == null)
665 msg.Headers.MessageId = new UniqueId ();
666 if (msg.Headers.ReplyTo == null)
667 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
668 if (msg.Headers.To == null && RemoteAddress != null)
669 msg.Headers.To = RemoteAddress.Uri;