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;
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)
93 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
96 throw new ArgumentNullException ("runtime");
97 if (messageVersion == null)
98 throw new ArgumentNullException ("messageVersion");
99 this.runtime = runtime;
100 this.remote_address = remoteAddress;
101 if (runtime.Via == null)
102 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
103 this.contract = contract;
104 this.message_version = messageVersion;
105 default_open_timeout = openTimeout;
106 default_close_timeout = closeTimeout;
107 _processDelegate = new ProcessDelegate (Process);
108 requestDelegate = new RequestDelegate (Request);
109 sendDelegate = new SendDelegate (Send);
112 AllowInitializationUI = true;
114 if (contextChannel != null)
115 channel = contextChannel;
117 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
119 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
120 this.factory = factory;
121 } catch (TargetInvocationException ex) {
122 if (ex.InnerException != null)
123 throw ex.InnerException;
130 public ContractDescription Contract {
131 get { return contract; }
134 public ClientRuntime Runtime {
135 get { return runtime; }
138 IRequestChannel RequestChannel {
139 get { return channel as IRequestChannel; }
142 IOutputChannel OutputChannel {
143 get { return channel as IOutputChannel; }
146 internal IDuplexChannel DuplexChannel {
147 get { return channel as IDuplexChannel; }
150 #region IClientChannel
152 bool did_interactive_initialization;
154 public bool AllowInitializationUI { get; set; }
156 public bool DidInteractiveInitialization {
157 get { return did_interactive_initialization; }
161 get { return runtime.Via; }
164 class DelegatingWaitHandle : WaitHandle
166 public DelegatingWaitHandle (IAsyncResult [] results)
168 this.results = results;
171 IAsyncResult [] results;
173 protected override void Dispose (bool disposing)
176 foreach (var r in results)
177 r.AsyncWaitHandle.Close ();
180 public override bool WaitOne ()
182 foreach (var r in results)
183 r.AsyncWaitHandle.WaitOne ();
187 public override bool WaitOne (int millisecondsTimeout)
189 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
192 WaitHandle [] ResultWaitHandles {
194 var arr = new WaitHandle [results.Length];
195 for (int i = 0; i < arr.Length; i++)
196 arr [i] = results [i].AsyncWaitHandle;
201 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
203 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
206 public override bool WaitOne (TimeSpan timeout, bool exitContext)
208 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
212 class DisplayUIAsyncResult : IAsyncResult
214 public DisplayUIAsyncResult (IAsyncResult [] results)
216 this.results = results;
219 IAsyncResult [] results;
221 internal IAsyncResult [] Results {
222 get { return results; }
225 public object AsyncState {
229 WaitHandle wait_handle;
231 public WaitHandle AsyncWaitHandle {
233 if (wait_handle == null)
234 wait_handle = new DelegatingWaitHandle (results);
239 public bool CompletedSynchronously {
241 foreach (var r in results)
242 if (!r.CompletedSynchronously)
247 public bool IsCompleted {
249 foreach (var r in results)
257 public IAsyncResult BeginDisplayInitializationUI (
258 AsyncCallback callback, object state)
260 OnInitializationUI ();
261 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
263 foreach (var init in runtime.InteractiveChannelInitializers)
264 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
265 return new DisplayUIAsyncResult (arr);
268 public void EndDisplayInitializationUI (
271 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
273 foreach (var init in runtime.InteractiveChannelInitializers)
274 init.EndDisplayInitializationUI (r.Results [i++]);
276 did_interactive_initialization = true;
279 public void DisplayInitializationUI ()
281 OnInitializationUI ();
282 foreach (var init in runtime.InteractiveChannelInitializers)
283 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
285 did_interactive_initialization = true;
288 void OnInitializationUI ()
290 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
291 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
294 public void Dispose ()
299 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
303 #region IContextChannel
306 public bool AllowOutputBatching { get; set; }
308 public IInputSession InputSession {
310 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
311 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
314 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
315 return dch != null ? dch.Session : null;
319 public EndpointAddress LocalAddress {
321 var dc = OperationChannel as IDuplexChannel;
322 return dc != null ? dc.LocalAddress : null;
326 public TimeSpan OperationTimeout {
328 if (!this.operation_timeout.HasValue) {
329 this.operation_timeout = DefaultCommunicationTimeouts.Instance.ReceiveTimeout;
331 return this.operation_timeout.Value;
334 this.operation_timeout = value;
338 public IOutputSession OutputSession {
340 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
341 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
344 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
345 return dch != null ? dch.Session : null;
349 public EndpointAddress RemoteAddress {
350 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
353 public string SessionId {
354 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
359 // CommunicationObject
360 protected internal override TimeSpan DefaultOpenTimeout {
361 get { return default_open_timeout; }
364 protected internal override TimeSpan DefaultCloseTimeout {
365 get { return default_close_timeout; }
368 protected override void OnAbort ()
371 if (factory != null) // ... is it valid?
375 Action<TimeSpan> close_delegate;
377 protected override IAsyncResult OnBeginClose (
378 TimeSpan timeout, AsyncCallback callback, object state)
380 if (close_delegate == null)
381 close_delegate = new Action<TimeSpan> (OnClose);
382 return close_delegate.BeginInvoke (timeout, callback, state);
385 protected override void OnEndClose (IAsyncResult result)
387 close_delegate.EndInvoke (result);
390 protected override void OnClose (TimeSpan timeout)
392 DateTime start = DateTime.Now;
393 if (channel.State == CommunicationState.Opened)
394 channel.Close (timeout);
397 Action<TimeSpan> open_callback;
399 protected override IAsyncResult OnBeginOpen (
400 TimeSpan timeout, AsyncCallback callback, object state)
402 if (open_callback == null)
403 open_callback = new Action<TimeSpan> (OnOpen);
404 return open_callback.BeginInvoke (timeout, callback, state);
407 protected override void OnEndOpen (IAsyncResult result)
409 if (open_callback == null)
410 throw new InvalidOperationException ("Async open operation has not started");
411 open_callback.EndInvoke (result);
414 protected override void OnOpen (TimeSpan timeout)
416 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
417 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
418 if (channel.State == CommunicationState.Created)
419 channel.Open (timeout);
424 IChannel OperationChannel {
425 get { return channel; }
428 public T GetProperty<T> () where T : class
430 if (typeof (T) == typeof (MessageVersion))
431 return (T) (object) message_version;
432 return OperationChannel.GetProperty<T> ();
435 // IExtensibleObject<IContextChannel>
437 IExtensionCollection<IContextChannel> extensions;
439 public IExtensionCollection<IContextChannel> Extensions {
441 if (extensions == null)
442 extensions = new ExtensionCollection<IContextChannel> (this);
447 #region Request/Output processing
449 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
452 var retval = _processDelegate.BeginInvoke (method, operationName, true, ref p, OperationContext.Current, callback, asyncState);
454 throw new InvalidOperationException ();
458 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
461 throw new ArgumentNullException ("result");
462 if (parameters == null)
463 throw new ArgumentNullException ("parameters");
465 object[] p = parameters;
466 var retval = _processDelegate.EndInvoke (ref p, result);
470 if (p.Length != parameters.Length)
471 throw new InvalidOperationException ();
472 Array.Copy (p, parameters, p.Length);
476 public object Process (MethodBase method, string operationName, object [] parameters, OperationContext context)
479 var retval = Process (method, operationName, false, ref p, context);
481 throw new InvalidOperationException ();
485 object Process (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context)
487 var previousContext = OperationContext.Current;
489 // Inherit the context from the calling thread
490 OperationContext.Current = context;
492 return DoProcess (method, operationName, isAsync, ref parameters, context);
493 } catch (Exception ex) {
496 // Reset the context before the thread goes back into the pool
497 OperationContext.Current = previousContext;
501 object DoProcess (MethodBase method, string operationName, bool isAsync, ref object [] parameters, OperationContext context)
503 if (AllowInitializationUI)
504 DisplayInitializationUI ();
505 OperationDescription od = SelectOperation (method, operationName, parameters);
507 if (State != CommunicationState.Opened)
511 return Request (od, isAsync, ref parameters, context);
513 Output (od, parameters, context);
518 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
521 if (Runtime.OperationSelector != null)
522 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
524 operation = operationName;
525 OperationDescription od = contract.Operations.Find (operation);
527 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
531 void Output (OperationDescription od, object [] parameters, OperationContext context)
533 ClientOperation op = runtime.Operations [od.Name];
534 Send (CreateRequest (op, parameters, context), OperationTimeout);
537 object Request (OperationDescription od, bool isAsync, ref object [] parameters, OperationContext context)
539 ClientOperation op = runtime.Operations [od.Name];
540 object [] inspections = new object [runtime.MessageInspectors.Count];
541 Message req = CreateRequest (op, parameters, context);
543 for (int i = 0; i < inspections.Length; i++)
544 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
546 Message res = Request (req, OperationTimeout);
548 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
549 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
550 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
552 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
553 if (fault.HasDetail) {
554 Type detailType = typeof (ExceptionDetail);
555 var freader = fault.GetReaderAtDetailContents ();
556 DataContractSerializer ds = null;
557 foreach (var fci in op.FaultContractInfos)
558 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
559 detailType = fci.Detail;
564 ds = new DataContractSerializer (detailType);
565 var detail = ds.ReadObject (freader);
566 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
570 ex = new FaultException (fault);
575 for (int i = 0; i < inspections.Length; i++)
576 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
578 if (!op.DeserializeReply)
581 if (isAsync && od.EndMethod != null) {
582 var endParams = od.EndMethod.GetParameters ();
583 parameters = new object [endParams.Length - 1];
586 return op.Formatter.DeserializeReply (res, parameters);
589 #region Message-based Request() and Send()
590 // They are internal for ClientBase<T>.ChannelBase use.
591 internal Message Request (Message msg, TimeSpan timeout)
593 if (RequestChannel != null)
594 return RequestChannel.Request (msg, timeout);
596 return RequestCorrelated (msg, timeout, OutputChannel);
599 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
601 // FIXME: implement ConcurrencyMode check:
602 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
604 DateTime startTime = DateTime.Now;
605 OutputChannel.Send (msg, timeout);
606 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
609 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
611 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
614 internal Message EndRequest (IAsyncResult result)
616 return requestDelegate.EndInvoke (result);
619 internal void Send (Message msg, TimeSpan timeout)
621 if (OutputChannel != null)
622 OutputChannel.Send (msg, timeout);
624 RequestChannel.Request (msg, timeout); // and ignore returned message.
627 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
629 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
632 internal void EndSend (IAsyncResult result)
634 sendDelegate.EndInvoke (result);
638 Message CreateRequest (ClientOperation op, object [] parameters, OperationContext context)
640 MessageVersion version = message_version;
642 version = MessageVersion.Default;
645 if (op.SerializeRequest)
646 msg = op.Formatter.SerializeRequest (
647 version, parameters);
649 if (parameters.Length != 1)
650 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));
651 if (!(parameters [0] is Message))
652 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
653 msg = (Message) parameters [0];
656 context = context ?? OperationContext.Current;
657 if (context != null) {
658 // CopyHeadersFrom does not work here (brings duplicates -> error)
659 foreach (var mh in context.OutgoingMessageHeaders) {
660 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
662 msg.Headers.RemoveAt (x);
663 msg.Headers.Add ((MessageHeader) mh);
665 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
668 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
669 //if (OutputSession != null)
670 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
671 msg.Properties.AllowOutputBatching = AllowOutputBatching;
673 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
674 if (msg.Headers.MessageId == null)
675 msg.Headers.MessageId = new UniqueId ();
676 if (msg.Headers.ReplyTo == null)
677 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
678 if (msg.Headers.To == null && RemoteAddress != null)
679 msg.Headers.To = RemoteAddress.Uri;