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;
207 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
209 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
212 public override bool WaitOne (TimeSpan timeout, bool exitContext)
214 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
218 class DisplayUIAsyncResult : IAsyncResult
220 public DisplayUIAsyncResult (IAsyncResult [] results)
222 this.results = results;
225 IAsyncResult [] results;
227 internal IAsyncResult [] Results {
228 get { return results; }
231 public object AsyncState {
235 WaitHandle wait_handle;
237 public WaitHandle AsyncWaitHandle {
239 if (wait_handle == null)
240 wait_handle = new DelegatingWaitHandle (results);
245 public bool CompletedSynchronously {
247 foreach (var r in results)
248 if (!r.CompletedSynchronously)
253 public bool IsCompleted {
255 foreach (var r in results)
263 public IAsyncResult BeginDisplayInitializationUI (
264 AsyncCallback callback, object state)
266 OnInitializationUI ();
267 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
269 foreach (var init in runtime.InteractiveChannelInitializers)
270 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
271 return new DisplayUIAsyncResult (arr);
274 public void EndDisplayInitializationUI (
277 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
279 foreach (var init in runtime.InteractiveChannelInitializers)
280 init.EndDisplayInitializationUI (r.Results [i++]);
282 did_interactive_initialization = true;
285 public void DisplayInitializationUI ()
287 OnInitializationUI ();
288 foreach (var init in runtime.InteractiveChannelInitializers)
289 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
291 did_interactive_initialization = true;
294 void OnInitializationUI ()
296 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
297 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
300 public void Dispose ()
305 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
309 #region IContextChannel
312 public bool AllowOutputBatching { get; set; }
314 public IInputSession InputSession {
316 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
317 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
320 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
321 return dch != null ? dch.Session : null;
325 public EndpointAddress LocalAddress {
327 var dc = OperationChannel as IDuplexChannel;
328 return dc != null ? dc.LocalAddress : null;
333 public TimeSpan OperationTimeout { get; set; }
335 public IOutputSession OutputSession {
337 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
338 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
341 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
342 return dch != null ? dch.Session : null;
346 public EndpointAddress RemoteAddress {
347 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
350 public string SessionId {
351 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
356 // CommunicationObject
357 protected internal override TimeSpan DefaultOpenTimeout {
358 get { return default_open_timeout; }
361 protected internal override TimeSpan DefaultCloseTimeout {
362 get { return default_close_timeout; }
365 protected override void OnAbort ()
368 if (factory != null) // ... is it valid?
372 Action<TimeSpan> close_delegate;
374 protected override IAsyncResult OnBeginClose (
375 TimeSpan timeout, AsyncCallback callback, object state)
377 if (close_delegate == null)
378 close_delegate = new Action<TimeSpan> (OnClose);
379 return close_delegate.BeginInvoke (timeout, callback, state);
382 protected override void OnEndClose (IAsyncResult result)
384 close_delegate.EndInvoke (result);
387 protected override void OnClose (TimeSpan timeout)
389 DateTime start = DateTime.Now;
390 if (channel.State == CommunicationState.Opened)
391 channel.Close (timeout);
394 Action<TimeSpan> open_callback;
396 protected override IAsyncResult OnBeginOpen (
397 TimeSpan timeout, AsyncCallback callback, object state)
399 if (open_callback == null)
400 open_callback = new Action<TimeSpan> (OnOpen);
401 return open_callback.BeginInvoke (timeout, callback, state);
404 protected override void OnEndOpen (IAsyncResult result)
406 if (open_callback == null)
407 throw new InvalidOperationException ("Async open operation has not started");
408 open_callback.EndInvoke (result);
411 protected override void OnOpen (TimeSpan timeout)
413 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
414 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
415 if (channel.State == CommunicationState.Created)
416 channel.Open (timeout);
421 IChannel OperationChannel {
422 get { return channel; }
425 public T GetProperty<T> () where T : class
427 if (typeof (T) == typeof (MessageVersion))
428 return (T) (object) message_version;
429 return OperationChannel.GetProperty<T> ();
432 // IExtensibleObject<IContextChannel>
434 IExtensionCollection<IContextChannel> extensions;
436 public IExtensionCollection<IContextChannel> Extensions {
438 if (extensions == null)
439 extensions = new ExtensionCollection<IContextChannel> (this);
444 #region Request/Output processing
446 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
449 throw new InvalidOperationException ("another operation is in progress");
450 context = OperationContext.Current;
453 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
460 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
464 throw new ArgumentNullException ("result");
465 if (parameters == null)
466 throw new ArgumentNullException ("parameters");
467 // FIXME: the method arguments should be verified to be
468 // identical to the arguments in the corresponding begin method.
469 return _processDelegate.EndInvoke (result);
475 public object Process (MethodBase method, string operationName, object [] parameters)
477 var previousContext = OperationContext.Current;
479 // Inherit the context from the calling thread
480 if (this.context != null)
481 OperationContext.Current = this.context;
483 return DoProcess (method, operationName, parameters);
484 } catch (Exception 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;