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 OperationContext Context { set; }
51 object Process (MethodBase method, string operationName, object [] parameters);
53 IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState);
55 object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result);
58 #if DISABLE_REAL_PROXY
59 // FIXME: This is a quick workaround for bug #571907
62 class ClientRuntimeChannel
63 : CommunicationObject, IClientChannel, IInternalContextChannel
65 ClientRuntime runtime;
66 EndpointAddress remote_address;
67 ContractDescription contract;
68 MessageVersion message_version;
69 TimeSpan default_open_timeout, default_close_timeout;
71 IChannelFactory factory;
72 OperationContext context;
75 readonly ProcessDelegate _processDelegate;
77 delegate object ProcessDelegate (MethodBase method, string operationName, object [] parameters);
79 readonly RequestDelegate requestDelegate;
81 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
83 readonly SendDelegate sendDelegate;
85 delegate void SendDelegate (Message msg, TimeSpan timeout);
88 public ClientRuntimeChannel (ServiceEndpoint endpoint,
89 ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
90 : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
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;
114 OperationTimeout = TimeSpan.FromMinutes (1);
116 if (contextChannel != null)
117 channel = contextChannel;
119 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
121 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
122 this.factory = factory;
123 } catch (TargetInvocationException ex) {
124 if (ex.InnerException != null)
125 throw ex.InnerException;
132 public ContractDescription Contract {
133 get { return contract; }
136 public ClientRuntime Runtime {
137 get { return runtime; }
140 IRequestChannel RequestChannel {
141 get { return channel as IRequestChannel; }
144 IOutputChannel OutputChannel {
145 get { return channel as IOutputChannel; }
148 internal IDuplexChannel DuplexChannel {
149 get { return channel as IDuplexChannel; }
152 public OperationContext Context {
153 set { context = value; }
156 #region IClientChannel
158 bool did_interactive_initialization;
160 public bool AllowInitializationUI { get; set; }
162 public bool DidInteractiveInitialization {
163 get { return did_interactive_initialization; }
167 get { return runtime.Via; }
170 class DelegatingWaitHandle : WaitHandle
172 public DelegatingWaitHandle (IAsyncResult [] results)
174 this.results = results;
177 IAsyncResult [] results;
179 protected override void Dispose (bool disposing)
182 foreach (var r in results)
183 r.AsyncWaitHandle.Close ();
186 public override bool WaitOne ()
188 foreach (var r in results)
189 r.AsyncWaitHandle.WaitOne ();
193 public override bool WaitOne (int millisecondsTimeout)
195 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
198 WaitHandle [] ResultWaitHandles {
200 var arr = new WaitHandle [results.Length];
201 for (int i = 0; i < arr.Length; i++)
202 arr [i] = results [i].AsyncWaitHandle;
208 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
210 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
213 public override bool WaitOne (TimeSpan timeout, bool exitContext)
215 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
220 class DisplayUIAsyncResult : IAsyncResult
222 public DisplayUIAsyncResult (IAsyncResult [] results)
224 this.results = results;
227 IAsyncResult [] results;
229 internal IAsyncResult [] Results {
230 get { return results; }
233 public object AsyncState {
237 WaitHandle wait_handle;
239 public WaitHandle AsyncWaitHandle {
241 if (wait_handle == null)
242 wait_handle = new DelegatingWaitHandle (results);
247 public bool CompletedSynchronously {
249 foreach (var r in results)
250 if (!r.CompletedSynchronously)
255 public bool IsCompleted {
257 foreach (var r in results)
265 public IAsyncResult BeginDisplayInitializationUI (
266 AsyncCallback callback, object state)
268 OnInitializationUI ();
269 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
271 foreach (var init in runtime.InteractiveChannelInitializers)
272 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
273 return new DisplayUIAsyncResult (arr);
276 public void EndDisplayInitializationUI (
279 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
281 foreach (var init in runtime.InteractiveChannelInitializers)
282 init.EndDisplayInitializationUI (r.Results [i++]);
284 did_interactive_initialization = true;
287 public void DisplayInitializationUI ()
289 OnInitializationUI ();
290 foreach (var init in runtime.InteractiveChannelInitializers)
291 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
293 did_interactive_initialization = true;
296 void OnInitializationUI ()
298 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
299 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
302 public void Dispose ()
307 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
311 #region IContextChannel
314 public bool AllowOutputBatching { get; set; }
316 public IInputSession InputSession {
318 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
319 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
322 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
323 return dch != null ? dch.Session : null;
327 public EndpointAddress LocalAddress {
329 var dc = OperationChannel as IDuplexChannel;
330 return dc != null ? dc.LocalAddress : null;
335 public TimeSpan OperationTimeout { get; set; }
337 public IOutputSession OutputSession {
339 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
340 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
343 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
344 return dch != null ? dch.Session : null;
348 public EndpointAddress RemoteAddress {
349 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
352 public string SessionId {
353 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
358 // CommunicationObject
359 protected internal override TimeSpan DefaultOpenTimeout {
360 get { return default_open_timeout; }
363 protected internal override TimeSpan DefaultCloseTimeout {
364 get { return default_close_timeout; }
367 protected override void OnAbort ()
370 if (factory != null) // ... is it valid?
374 Action<TimeSpan> close_delegate;
376 protected override IAsyncResult OnBeginClose (
377 TimeSpan timeout, AsyncCallback callback, object state)
379 if (close_delegate == null)
380 close_delegate = new Action<TimeSpan> (OnClose);
381 return close_delegate.BeginInvoke (timeout, callback, state);
384 protected override void OnEndClose (IAsyncResult result)
386 close_delegate.EndInvoke (result);
389 protected override void OnClose (TimeSpan timeout)
391 DateTime start = DateTime.Now;
392 if (channel.State == CommunicationState.Opened)
393 channel.Close (timeout);
396 Action<TimeSpan> open_callback;
398 protected override IAsyncResult OnBeginOpen (
399 TimeSpan timeout, AsyncCallback callback, object state)
401 if (open_callback == null)
402 open_callback = new Action<TimeSpan> (OnOpen);
403 return open_callback.BeginInvoke (timeout, callback, state);
406 protected override void OnEndOpen (IAsyncResult result)
408 if (open_callback == null)
409 throw new InvalidOperationException ("Async open operation has not started");
410 open_callback.EndInvoke (result);
413 protected override void OnOpen (TimeSpan timeout)
415 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
416 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
417 if (channel.State == CommunicationState.Created)
418 channel.Open (timeout);
423 IChannel OperationChannel {
424 get { return channel; }
427 public T GetProperty<T> () where T : class
429 if (typeof (T) == typeof (MessageVersion))
430 return (T) (object) message_version;
431 return OperationChannel.GetProperty<T> ();
434 // IExtensibleObject<IContextChannel>
436 IExtensionCollection<IContextChannel> extensions;
438 public IExtensionCollection<IContextChannel> Extensions {
440 if (extensions == null)
441 extensions = new ExtensionCollection<IContextChannel> (this);
446 #region Request/Output processing
448 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
451 throw new InvalidOperationException ("another operation is in progress");
452 context = OperationContext.Current;
454 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
457 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");
464 // FIXME: the method arguments should be verified to be
465 // identical to the arguments in the corresponding begin method.
466 object asyncResult = _processDelegate.EndInvoke (result);
471 public object Process (MethodBase method, string operationName, object [] parameters)
473 var previousContext = OperationContext.Current;
475 // Inherit the context from the calling thread
476 if (this.context != null)
477 OperationContext.Current = this.context;
479 return DoProcess (method, operationName, parameters);
480 } catch (Exception ex) {
481 #if MOONLIGHT // just for debugging
482 Console.Write ("Exception in async operation: ");
483 Console.WriteLine (ex);
487 // Reset the context before the thread goes back into the pool
488 OperationContext.Current = previousContext;
492 object DoProcess (MethodBase method, string operationName, object [] parameters)
494 if (AllowInitializationUI)
495 DisplayInitializationUI ();
496 OperationDescription od = SelectOperation (method, operationName, parameters);
498 if (State != CommunicationState.Opened)
502 return Request (od, parameters);
504 Output (od, parameters);
509 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
512 if (Runtime.OperationSelector != null)
513 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
515 operation = operationName;
516 OperationDescription od = contract.Operations.Find (operation);
518 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
522 void Output (OperationDescription od, object [] parameters)
524 ClientOperation op = runtime.Operations [od.Name];
525 Send (CreateRequest (op, parameters), OperationTimeout);
528 object Request (OperationDescription od, object [] parameters)
530 ClientOperation op = runtime.Operations [od.Name];
531 object [] inspections = new object [runtime.MessageInspectors.Count];
532 Message req = CreateRequest (op, parameters);
534 for (int i = 0; i < inspections.Length; i++)
535 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
537 Message res = Request (req, OperationTimeout);
539 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
540 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
541 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
543 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
544 if (fault.HasDetail) {
545 Type detailType = typeof (ExceptionDetail);
546 var freader = fault.GetReaderAtDetailContents ();
547 DataContractSerializer ds = null;
548 foreach (var fci in op.FaultContractInfos)
549 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
550 detailType = fci.Detail;
555 ds = new DataContractSerializer (detailType);
556 var detail = ds.ReadObject (freader);
557 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
561 ex = new FaultException (fault);
566 for (int i = 0; i < inspections.Length; i++)
567 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
569 if (op.DeserializeReply)
570 return op.Formatter.DeserializeReply (res, parameters);
575 #region Message-based Request() and Send()
576 // They are internal for ClientBase<T>.ChannelBase use.
577 internal Message Request (Message msg, TimeSpan timeout)
579 if (RequestChannel != null)
580 return RequestChannel.Request (msg, timeout);
582 return RequestCorrelated (msg, timeout, OutputChannel);
585 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
587 // FIXME: implement ConcurrencyMode check:
588 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
590 DateTime startTime = DateTime.Now;
591 OutputChannel.Send (msg, timeout);
592 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
595 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
597 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
600 internal Message EndRequest (IAsyncResult result)
602 return requestDelegate.EndInvoke (result);
605 internal void Send (Message msg, TimeSpan timeout)
607 if (OutputChannel != null)
608 OutputChannel.Send (msg, timeout);
610 RequestChannel.Request (msg, timeout); // and ignore returned message.
613 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
615 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
618 internal void EndSend (IAsyncResult result)
620 sendDelegate.EndInvoke (result);
624 Message CreateRequest (ClientOperation op, object [] parameters)
626 MessageVersion version = message_version;
628 version = MessageVersion.Default;
631 if (op.SerializeRequest)
632 msg = op.Formatter.SerializeRequest (
633 version, parameters);
635 if (parameters.Length != 1)
636 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));
637 if (!(parameters [0] is Message))
638 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
639 msg = (Message) parameters [0];
642 context = context ?? OperationContext.Current;
643 if (context != null) {
644 // CopyHeadersFrom does not work here (brings duplicates -> error)
645 foreach (var mh in context.OutgoingMessageHeaders) {
646 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
648 msg.Headers.RemoveAt (x);
649 msg.Headers.Add ((MessageHeader) mh);
651 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
654 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
655 //if (OutputSession != null)
656 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
657 msg.Properties.AllowOutputBatching = AllowOutputBatching;
659 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
660 if (msg.Headers.MessageId == null)
661 msg.Headers.MessageId = new UniqueId ();
662 if (msg.Headers.ReplyTo == null)
663 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
664 if (msg.Headers.To == null && RemoteAddress != null)
665 msg.Headers.To = RemoteAddress.Uri;