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);
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 OperationContext context;
73 readonly ProcessDelegate _processDelegate;
75 delegate object ProcessDelegate (MethodBase method, string operationName, object [] parameters);
77 readonly RequestDelegate requestDelegate;
79 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
81 readonly SendDelegate sendDelegate;
83 delegate void SendDelegate (Message msg, TimeSpan timeout);
86 public ClientRuntimeChannel (ServiceEndpoint endpoint,
87 ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
88 : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
92 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
95 throw new ArgumentNullException ("runtime");
96 if (messageVersion == null)
97 throw new ArgumentNullException ("messageVersion");
98 this.runtime = runtime;
99 this.remote_address = remoteAddress;
100 if (runtime.Via == null)
101 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
102 this.contract = contract;
103 this.message_version = messageVersion;
104 default_open_timeout = openTimeout;
105 default_close_timeout = closeTimeout;
106 _processDelegate = new ProcessDelegate (Process);
107 requestDelegate = new RequestDelegate (Request);
108 sendDelegate = new SendDelegate (Send);
111 AllowInitializationUI = true;
112 OperationTimeout = TimeSpan.FromMinutes (1);
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;
202 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
204 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
207 public override bool WaitOne (TimeSpan timeout, bool exitContext)
209 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
214 class DisplayUIAsyncResult : IAsyncResult
216 public DisplayUIAsyncResult (IAsyncResult [] results)
218 this.results = results;
221 IAsyncResult [] results;
223 internal IAsyncResult [] Results {
224 get { return results; }
227 public object AsyncState {
231 WaitHandle wait_handle;
233 public WaitHandle AsyncWaitHandle {
235 if (wait_handle == null)
236 wait_handle = new DelegatingWaitHandle (results);
241 public bool CompletedSynchronously {
243 foreach (var r in results)
244 if (!r.CompletedSynchronously)
249 public bool IsCompleted {
251 foreach (var r in results)
259 public IAsyncResult BeginDisplayInitializationUI (
260 AsyncCallback callback, object state)
262 OnInitializationUI ();
263 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
265 foreach (var init in runtime.InteractiveChannelInitializers)
266 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
267 return new DisplayUIAsyncResult (arr);
270 public void EndDisplayInitializationUI (
273 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
275 foreach (var init in runtime.InteractiveChannelInitializers)
276 init.EndDisplayInitializationUI (r.Results [i++]);
278 did_interactive_initialization = true;
281 public void DisplayInitializationUI ()
283 OnInitializationUI ();
284 foreach (var init in runtime.InteractiveChannelInitializers)
285 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
287 did_interactive_initialization = true;
290 void OnInitializationUI ()
292 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
293 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
296 public void Dispose ()
301 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
305 #region IContextChannel
308 public bool AllowOutputBatching { get; set; }
310 public IInputSession InputSession {
312 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
313 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
316 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
317 return dch != null ? dch.Session : null;
321 public EndpointAddress LocalAddress {
323 var dc = OperationChannel as IDuplexChannel;
324 return dc != null ? dc.LocalAddress : null;
329 public TimeSpan OperationTimeout { get; set; }
331 public IOutputSession OutputSession {
333 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
334 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
337 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
338 return dch != null ? dch.Session : null;
342 public EndpointAddress RemoteAddress {
343 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
346 public string SessionId {
347 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
352 // CommunicationObject
353 protected internal override TimeSpan DefaultOpenTimeout {
354 get { return default_open_timeout; }
357 protected internal override TimeSpan DefaultCloseTimeout {
358 get { return default_close_timeout; }
361 protected override void OnAbort ()
364 if (factory != null) // ... is it valid?
368 Action<TimeSpan> close_delegate;
370 protected override IAsyncResult OnBeginClose (
371 TimeSpan timeout, AsyncCallback callback, object state)
373 if (close_delegate == null)
374 close_delegate = new Action<TimeSpan> (OnClose);
375 return close_delegate.BeginInvoke (timeout, callback, state);
378 protected override void OnEndClose (IAsyncResult result)
380 close_delegate.EndInvoke (result);
383 protected override void OnClose (TimeSpan timeout)
385 DateTime start = DateTime.Now;
386 if (channel.State == CommunicationState.Opened)
387 channel.Close (timeout);
390 Action<TimeSpan> open_callback;
392 protected override IAsyncResult OnBeginOpen (
393 TimeSpan timeout, AsyncCallback callback, object state)
395 if (open_callback == null)
396 open_callback = new Action<TimeSpan> (OnOpen);
397 return open_callback.BeginInvoke (timeout, callback, state);
400 protected override void OnEndOpen (IAsyncResult result)
402 if (open_callback == null)
403 throw new InvalidOperationException ("Async open operation has not started");
404 open_callback.EndInvoke (result);
407 protected override void OnOpen (TimeSpan timeout)
409 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
410 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
411 if (channel.State == CommunicationState.Created)
412 channel.Open (timeout);
417 IChannel OperationChannel {
418 get { return channel; }
421 public T GetProperty<T> () where T : class
423 if (typeof (T) == typeof (MessageVersion))
424 return (T) (object) message_version;
425 return OperationChannel.GetProperty<T> ();
428 // IExtensibleObject<IContextChannel>
430 IExtensionCollection<IContextChannel> extensions;
432 public IExtensionCollection<IContextChannel> Extensions {
434 if (extensions == null)
435 extensions = new ExtensionCollection<IContextChannel> (this);
440 #region Request/Output processing
442 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
445 throw new InvalidOperationException ("another operation is in progress");
446 context = OperationContext.Current;
448 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
451 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
455 throw new ArgumentNullException ("result");
456 if (parameters == null)
457 throw new ArgumentNullException ("parameters");
458 // FIXME: the method arguments should be verified to be
459 // identical to the arguments in the corresponding begin method.
460 return _processDelegate.EndInvoke (result);
463 public object Process (MethodBase method, string operationName, object [] parameters)
466 return DoProcess (method, operationName, parameters);
467 } catch (Exception ex) {
468 #if MOONLIGHT // just for debugging
469 Console.Write ("Exception in async operation: ");
470 Console.WriteLine (ex);
476 object DoProcess (MethodBase method, string operationName, object [] parameters)
478 if (AllowInitializationUI)
479 DisplayInitializationUI ();
480 OperationDescription od = SelectOperation (method, operationName, parameters);
482 if (State != CommunicationState.Opened)
486 return Request (od, parameters);
488 Output (od, parameters);
493 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
496 if (Runtime.OperationSelector != null)
497 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
499 operation = operationName;
500 OperationDescription od = contract.Operations.Find (operation);
502 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
506 void Output (OperationDescription od, object [] parameters)
508 ClientOperation op = runtime.Operations [od.Name];
509 Send (CreateRequest (op, parameters), OperationTimeout);
512 object Request (OperationDescription od, object [] parameters)
514 ClientOperation op = runtime.Operations [od.Name];
515 object [] inspections = new object [runtime.MessageInspectors.Count];
516 Message req = CreateRequest (op, parameters);
518 for (int i = 0; i < inspections.Length; i++)
519 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
521 Message res = Request (req, OperationTimeout);
523 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
524 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
525 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
527 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
528 if (fault.HasDetail) {
529 Type detailType = typeof (ExceptionDetail);
530 var freader = fault.GetReaderAtDetailContents ();
531 DataContractSerializer ds = null;
533 foreach (var fci in op.FaultContractInfos)
534 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
535 detailType = fci.Detail;
541 ds = new DataContractSerializer (detailType);
542 var detail = ds.ReadObject (freader);
543 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
547 ex = new FaultException (fault);
552 for (int i = 0; i < inspections.Length; i++)
553 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
555 if (op.DeserializeReply)
556 return op.Formatter.DeserializeReply (res, parameters);
561 #region Message-based Request() and Send()
562 // They are internal for ClientBase<T>.ChannelBase use.
563 internal Message Request (Message msg, TimeSpan timeout)
565 if (RequestChannel != null)
566 return RequestChannel.Request (msg, timeout);
568 return RequestCorrelated (msg, timeout, OutputChannel);
571 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
573 // FIXME: implement ConcurrencyMode check:
574 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
576 DateTime startTime = DateTime.Now;
577 OutputChannel.Send (msg, timeout);
578 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
581 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
583 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
586 internal Message EndRequest (IAsyncResult result)
588 return requestDelegate.EndInvoke (result);
591 internal void Send (Message msg, TimeSpan timeout)
593 if (OutputChannel != null)
594 OutputChannel.Send (msg, timeout);
596 RequestChannel.Request (msg, timeout); // and ignore returned message.
599 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
601 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
604 internal void EndSend (IAsyncResult result)
606 sendDelegate.EndInvoke (result);
610 Message CreateRequest (ClientOperation op, object [] parameters)
612 MessageVersion version = message_version;
614 version = MessageVersion.Default;
617 if (op.SerializeRequest)
618 msg = op.Formatter.SerializeRequest (
619 version, parameters);
621 if (parameters.Length != 1)
622 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));
623 if (!(parameters [0] is Message))
624 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
625 msg = (Message) parameters [0];
628 context = context ?? OperationContext.Current;
629 if (context != null) {
630 // CopyHeadersFrom does not work here (brings duplicates -> error)
631 foreach (var mh in context.OutgoingMessageHeaders) {
632 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
634 msg.Headers.RemoveAt (x);
635 msg.Headers.Add ((MessageHeader) mh);
637 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
640 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
641 //if (OutputSession != null)
642 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
643 msg.Properties.AllowOutputBatching = AllowOutputBatching;
645 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
646 if (msg.Headers.MessageId == null)
647 msg.Headers.MessageId = new UniqueId ();
648 if (msg.Headers.ReplyTo == null)
649 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
650 if (msg.Headers.To == null && RemoteAddress != null)
651 msg.Headers.To = RemoteAddress.Uri;