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.Reflection;
30 using System.Runtime.Serialization;
31 using System.ServiceModel.Channels;
32 using System.ServiceModel.Description;
33 using System.ServiceModel.Dispatcher;
34 using System.ServiceModel.Security;
35 using System.Threading;
38 namespace System.ServiceModel.MonoInternal
40 // FIXME: This is a quick workaround for bug #571907
41 public class ClientRuntimeChannel
42 : CommunicationObject, IClientChannel
44 ClientRuntime runtime;
45 EndpointAddress remote_address;
46 ContractDescription contract;
47 MessageVersion message_version;
48 TimeSpan default_open_timeout, default_close_timeout;
50 IChannelFactory factory;
51 OperationContext context;
54 readonly ProcessDelegate _processDelegate;
56 delegate object ProcessDelegate (MethodBase method, string operationName, object [] parameters);
58 readonly RequestDelegate requestDelegate;
60 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
62 readonly SendDelegate sendDelegate;
64 delegate void SendDelegate (Message msg, TimeSpan timeout);
67 public ClientRuntimeChannel (ServiceEndpoint endpoint,
68 ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
69 : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
73 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
76 throw new ArgumentNullException ("runtime");
77 this.runtime = runtime;
78 this.remote_address = remoteAddress;
79 if (runtime.Via == null)
80 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
81 this.contract = contract;
82 this.message_version = messageVersion;
83 default_open_timeout = openTimeout;
84 default_close_timeout = closeTimeout;
85 _processDelegate = new ProcessDelegate (Process);
86 requestDelegate = new RequestDelegate (Request);
87 sendDelegate = new SendDelegate (Send);
90 AllowInitializationUI = true;
91 OperationTimeout = TimeSpan.FromMinutes (1);
93 if (contextChannel != null)
94 channel = contextChannel;
96 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
98 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
99 this.factory = factory;
100 } catch (TargetInvocationException ex) {
101 if (ex.InnerException != null)
102 throw ex.InnerException;
109 public ContractDescription Contract {
110 get { return contract; }
113 public ClientRuntime Runtime {
114 get { return runtime; }
117 IRequestChannel RequestChannel {
118 get { return channel as IRequestChannel; }
121 IOutputChannel OutputChannel {
122 get { return channel as IOutputChannel; }
125 internal IDuplexChannel DuplexChannel {
126 get { return channel as IDuplexChannel; }
129 #region IClientChannel
131 bool did_interactive_initialization;
133 public bool AllowInitializationUI { get; set; }
135 public bool DidInteractiveInitialization {
136 get { return did_interactive_initialization; }
140 get { return runtime.Via; }
143 class DelegatingWaitHandle : WaitHandle
145 public DelegatingWaitHandle (IAsyncResult [] results)
147 this.results = results;
150 IAsyncResult [] results;
152 protected override void Dispose (bool disposing)
155 foreach (var r in results)
156 r.AsyncWaitHandle.Close ();
159 public override bool WaitOne ()
161 foreach (var r in results)
162 r.AsyncWaitHandle.WaitOne ();
166 public override bool WaitOne (int millisecondsTimeout)
168 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
171 WaitHandle [] ResultWaitHandles {
173 var arr = new WaitHandle [results.Length];
174 for (int i = 0; i < arr.Length; i++)
175 arr [i] = results [i].AsyncWaitHandle;
181 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
183 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
186 public override bool WaitOne (TimeSpan timeout, bool exitContext)
188 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
193 class DisplayUIAsyncResult : IAsyncResult
195 public DisplayUIAsyncResult (IAsyncResult [] results)
197 this.results = results;
200 IAsyncResult [] results;
202 internal IAsyncResult [] Results {
203 get { return results; }
206 public object AsyncState {
210 WaitHandle wait_handle;
212 public WaitHandle AsyncWaitHandle {
214 if (wait_handle == null)
215 wait_handle = new DelegatingWaitHandle (results);
220 public bool CompletedSynchronously {
222 foreach (var r in results)
223 if (!r.CompletedSynchronously)
228 public bool IsCompleted {
230 foreach (var r in results)
238 public IAsyncResult BeginDisplayInitializationUI (
239 AsyncCallback callback, object state)
241 OnInitializationUI ();
242 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
244 foreach (var init in runtime.InteractiveChannelInitializers)
245 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
246 return new DisplayUIAsyncResult (arr);
249 public void EndDisplayInitializationUI (
252 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
254 foreach (var init in runtime.InteractiveChannelInitializers)
255 init.EndDisplayInitializationUI (r.Results [i++]);
257 did_interactive_initialization = true;
260 public void DisplayInitializationUI ()
262 OnInitializationUI ();
263 foreach (var init in runtime.InteractiveChannelInitializers)
264 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
266 did_interactive_initialization = true;
269 void OnInitializationUI ()
271 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
272 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
275 public void Dispose ()
280 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
284 #region IContextChannel
287 public bool AllowOutputBatching { get; set; }
289 public IInputSession InputSession {
291 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
292 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
295 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
296 return dch != null ? dch.Session : null;
300 public EndpointAddress LocalAddress {
302 var dc = OperationChannel as IDuplexChannel;
303 return dc != null ? dc.LocalAddress : null;
308 public TimeSpan OperationTimeout { get; set; }
310 public IOutputSession OutputSession {
312 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
313 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
316 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
317 return dch != null ? dch.Session : null;
321 public EndpointAddress RemoteAddress {
322 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
325 public string SessionId {
326 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
331 // CommunicationObject
332 protected internal override TimeSpan DefaultOpenTimeout {
333 get { return default_open_timeout; }
336 protected internal override TimeSpan DefaultCloseTimeout {
337 get { return default_close_timeout; }
340 protected override void OnAbort ()
343 if (factory != null) // ... is it valid?
347 Action<TimeSpan> close_delegate;
349 protected override IAsyncResult OnBeginClose (
350 TimeSpan timeout, AsyncCallback callback, object state)
352 if (close_delegate == null)
353 close_delegate = new Action<TimeSpan> (OnClose);
354 return close_delegate.BeginInvoke (timeout, callback, state);
357 protected override void OnEndClose (IAsyncResult result)
359 close_delegate.EndInvoke (result);
362 protected override void OnClose (TimeSpan timeout)
364 DateTime start = DateTime.Now;
365 channel.Close (timeout);
368 Action<TimeSpan> open_callback;
370 protected override IAsyncResult OnBeginOpen (
371 TimeSpan timeout, AsyncCallback callback, object state)
373 if (open_callback == null)
374 open_callback = new Action<TimeSpan> (OnOpen);
375 return open_callback.BeginInvoke (timeout, callback, state);
378 protected override void OnEndOpen (IAsyncResult result)
380 if (open_callback == null)
381 throw new InvalidOperationException ("Async open operation has not started");
382 open_callback.EndInvoke (result);
385 protected override void OnOpen (TimeSpan timeout)
387 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
388 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
389 if (channel.State == CommunicationState.Created)
390 channel.Open (timeout);
395 IChannel OperationChannel {
396 get { return channel; }
399 public T GetProperty<T> () where T : class
401 return OperationChannel.GetProperty<T> ();
404 // IExtensibleObject<IContextChannel>
406 IExtensionCollection<IContextChannel> extensions;
408 public IExtensionCollection<IContextChannel> Extensions {
410 if (extensions == null)
411 extensions = new ExtensionCollection<IContextChannel> (this);
416 #region Request/Output processing
418 class TempAsyncResult : IAsyncResult
420 public TempAsyncResult (object returnValue, object state)
422 ReturnValue = returnValue;
424 CompletedSynchronously = true;
426 AsyncWaitHandle = new ManualResetEvent (true);
429 public object ReturnValue { get; set; }
430 public object AsyncState { get; set; }
431 public bool CompletedSynchronously { get; set; }
432 public bool IsCompleted { get; set; }
433 public WaitHandle AsyncWaitHandle { get; set; }
436 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
439 throw new InvalidOperationException ("another operation is in progress");
440 context = OperationContext.Current;
442 // FIXME: this is a workaround for bug #633945
443 switch (Environment.OSVersion.Platform) {
444 case PlatformID.Unix:
445 case PlatformID.MacOSX:
446 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
448 var result = Process (method, operationName, parameters);
449 var ret = new TempAsyncResult (result, asyncState);
450 if (callback != null)
456 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
460 throw new ArgumentNullException ("result");
461 if (parameters == null)
462 throw new ArgumentNullException ("parameters");
463 // FIXME: the method arguments should be verified to be
464 // identical to the arguments in the corresponding begin method.
465 // FIXME: this is a workaround for bug #633945
466 switch (Environment.OSVersion.Platform) {
467 case PlatformID.Unix:
468 case PlatformID.MacOSX:
469 return _processDelegate.EndInvoke (result);
471 return ((TempAsyncResult) result).ReturnValue;
475 public object Process (MethodBase method, string operationName, object [] parameters)
478 return DoProcess (method, operationName, parameters);
479 } catch (Exception ex) {
480 #if MOONLIGHT // just for debugging
481 Console.Write ("Exception in async operation: ");
482 Console.WriteLine (ex);
488 object DoProcess (MethodBase method, string operationName, object [] parameters)
490 if (AllowInitializationUI)
491 DisplayInitializationUI ();
492 OperationDescription od = SelectOperation (method, operationName, parameters);
494 if (State != CommunicationState.Opened)
498 return Request (od, parameters);
500 Output (od, parameters);
505 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
508 if (Runtime.OperationSelector != null)
509 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
511 operation = operationName;
512 OperationDescription od = contract.Operations.Find (operation);
514 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
518 void Output (OperationDescription od, object [] parameters)
520 ClientOperation op = runtime.Operations [od.Name];
521 Send (CreateRequest (op, parameters), OperationTimeout);
524 object Request (OperationDescription od, object [] parameters)
526 ClientOperation op = runtime.Operations [od.Name];
527 object [] inspections = new object [runtime.MessageInspectors.Count];
528 Message req = CreateRequest (op, parameters);
530 for (int i = 0; i < inspections.Length; i++)
531 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
533 Message res = Request (req, OperationTimeout);
535 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
536 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
537 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
539 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
540 if (fault.HasDetail) {
541 Type detailType = typeof (ExceptionDetail);
542 var freader = fault.GetReaderAtDetailContents ();
543 DataContractSerializer ds = null;
545 foreach (var fci in op.FaultContractInfos)
546 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
547 detailType = fci.Detail;
553 ds = new DataContractSerializer (detailType);
554 var detail = ds.ReadObject (freader);
555 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
559 ex = new FaultException (fault);
564 for (int i = 0; i < inspections.Length; i++)
565 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
567 if (op.DeserializeReply)
568 return op.Formatter.DeserializeReply (res, parameters);
573 #region Message-based Request() and Send()
574 // They are internal for ClientBase<T>.ChannelBase use.
575 internal Message Request (Message msg, TimeSpan timeout)
577 if (RequestChannel != null)
578 return RequestChannel.Request (msg, timeout);
580 DateTime startTime = DateTime.Now;
581 OutputChannel.Send (msg, timeout);
582 return ((IDuplexChannel) OutputChannel).Receive (timeout - (DateTime.Now - startTime));
586 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
588 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
591 internal Message EndRequest (IAsyncResult result)
593 return requestDelegate.EndInvoke (result);
596 internal void Send (Message msg, TimeSpan timeout)
598 if (OutputChannel != null)
599 OutputChannel.Send (msg, timeout);
601 RequestChannel.Request (msg, timeout); // and ignore returned message.
604 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
606 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
609 internal void EndSend (IAsyncResult result)
611 sendDelegate.EndInvoke (result);
615 Message CreateRequest (ClientOperation op, object [] parameters)
617 MessageVersion version = message_version;
619 version = MessageVersion.Default;
622 if (op.SerializeRequest)
623 msg = op.Formatter.SerializeRequest (
624 version, parameters);
626 if (parameters.Length != 1)
627 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));
628 if (!(parameters [0] is Message))
629 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
630 msg = (Message) parameters [0];
633 context = context ?? OperationContext.Current;
634 if (context != null) {
635 // CopyHeadersFrom does not work here (brings duplicates -> error)
636 foreach (var mh in context.OutgoingMessageHeaders) {
637 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
639 msg.Headers.RemoveAt (x);
640 msg.Headers.Add ((MessageHeader) mh);
642 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
645 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
646 //if (OutputSession != null)
647 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
648 msg.Properties.AllowOutputBatching = AllowOutputBatching;
650 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
651 if (msg.Headers.MessageId == null)
652 msg.Headers.MessageId = new UniqueId ();
653 if (msg.Headers.ReplyTo == null)
654 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
655 if (msg.Headers.To == null && RemoteAddress != null)
656 msg.Headers.To = RemoteAddress.Uri;