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;
72 readonly ProcessDelegate _processDelegate;
74 delegate object ProcessDelegate (MethodBase method, string operationName, object [] parameters, OperationContext context);
76 readonly RequestDelegate requestDelegate;
78 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
80 readonly SendDelegate sendDelegate;
82 delegate void SendDelegate (Message msg, TimeSpan timeout);
85 public ClientRuntimeChannel (ServiceEndpoint endpoint,
86 ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
87 : this (endpoint.CreateClientRuntime (null), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
91 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
94 throw new ArgumentNullException ("runtime");
95 if (messageVersion == null)
96 throw new ArgumentNullException ("messageVersion");
97 this.runtime = runtime;
98 this.remote_address = remoteAddress;
99 if (runtime.Via == null)
100 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
101 this.contract = contract;
102 this.message_version = messageVersion;
103 default_open_timeout = openTimeout;
104 default_close_timeout = closeTimeout;
105 _processDelegate = new ProcessDelegate (Process);
106 requestDelegate = new RequestDelegate (Request);
107 sendDelegate = new SendDelegate (Send);
110 AllowInitializationUI = true;
111 OperationTimeout = TimeSpan.FromMinutes (1);
113 if (contextChannel != null)
114 channel = contextChannel;
116 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
118 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
119 this.factory = factory;
120 } catch (TargetInvocationException ex) {
121 if (ex.InnerException != null)
122 throw ex.InnerException;
129 public ContractDescription Contract {
130 get { return contract; }
133 public ClientRuntime Runtime {
134 get { return runtime; }
137 IRequestChannel RequestChannel {
138 get { return channel as IRequestChannel; }
141 IOutputChannel OutputChannel {
142 get { return channel as IOutputChannel; }
145 internal IDuplexChannel DuplexChannel {
146 get { return channel as IDuplexChannel; }
149 #region IClientChannel
151 bool did_interactive_initialization;
153 public bool AllowInitializationUI { get; set; }
155 public bool DidInteractiveInitialization {
156 get { return did_interactive_initialization; }
160 get { return runtime.Via; }
163 class DelegatingWaitHandle : WaitHandle
165 public DelegatingWaitHandle (IAsyncResult [] results)
167 this.results = results;
170 IAsyncResult [] results;
172 protected override void Dispose (bool disposing)
175 foreach (var r in results)
176 r.AsyncWaitHandle.Close ();
179 public override bool WaitOne ()
181 foreach (var r in results)
182 r.AsyncWaitHandle.WaitOne ();
186 public override bool WaitOne (int millisecondsTimeout)
188 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
191 WaitHandle [] ResultWaitHandles {
193 var arr = new WaitHandle [results.Length];
194 for (int i = 0; i < arr.Length; i++)
195 arr [i] = results [i].AsyncWaitHandle;
200 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
202 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
205 public override bool WaitOne (TimeSpan timeout, bool exitContext)
207 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
211 class DisplayUIAsyncResult : IAsyncResult
213 public DisplayUIAsyncResult (IAsyncResult [] results)
215 this.results = results;
218 IAsyncResult [] results;
220 internal IAsyncResult [] Results {
221 get { return results; }
224 public object AsyncState {
228 WaitHandle wait_handle;
230 public WaitHandle AsyncWaitHandle {
232 if (wait_handle == null)
233 wait_handle = new DelegatingWaitHandle (results);
238 public bool CompletedSynchronously {
240 foreach (var r in results)
241 if (!r.CompletedSynchronously)
246 public bool IsCompleted {
248 foreach (var r in results)
256 public IAsyncResult BeginDisplayInitializationUI (
257 AsyncCallback callback, object state)
259 OnInitializationUI ();
260 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
262 foreach (var init in runtime.InteractiveChannelInitializers)
263 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
264 return new DisplayUIAsyncResult (arr);
267 public void EndDisplayInitializationUI (
270 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
272 foreach (var init in runtime.InteractiveChannelInitializers)
273 init.EndDisplayInitializationUI (r.Results [i++]);
275 did_interactive_initialization = true;
278 public void DisplayInitializationUI ()
280 OnInitializationUI ();
281 foreach (var init in runtime.InteractiveChannelInitializers)
282 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
284 did_interactive_initialization = true;
287 void OnInitializationUI ()
289 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
290 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
293 public void Dispose ()
298 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
302 #region IContextChannel
305 public bool AllowOutputBatching { get; set; }
307 public IInputSession InputSession {
309 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
310 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
313 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
314 return dch != null ? dch.Session : null;
318 public EndpointAddress LocalAddress {
320 var dc = OperationChannel as IDuplexChannel;
321 return dc != null ? dc.LocalAddress : null;
326 public TimeSpan OperationTimeout { get; set; }
328 public IOutputSession OutputSession {
330 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
331 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
334 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
335 return dch != null ? dch.Session : null;
339 public EndpointAddress RemoteAddress {
340 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
343 public string SessionId {
344 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
349 // CommunicationObject
350 protected internal override TimeSpan DefaultOpenTimeout {
351 get { return default_open_timeout; }
354 protected internal override TimeSpan DefaultCloseTimeout {
355 get { return default_close_timeout; }
358 protected override void OnAbort ()
361 if (factory != null) // ... is it valid?
365 Action<TimeSpan> close_delegate;
367 protected override IAsyncResult OnBeginClose (
368 TimeSpan timeout, AsyncCallback callback, object state)
370 if (close_delegate == null)
371 close_delegate = new Action<TimeSpan> (OnClose);
372 return close_delegate.BeginInvoke (timeout, callback, state);
375 protected override void OnEndClose (IAsyncResult result)
377 close_delegate.EndInvoke (result);
380 protected override void OnClose (TimeSpan timeout)
382 DateTime start = DateTime.Now;
383 if (channel.State == CommunicationState.Opened)
384 channel.Close (timeout);
387 Action<TimeSpan> open_callback;
389 protected override IAsyncResult OnBeginOpen (
390 TimeSpan timeout, AsyncCallback callback, object state)
392 if (open_callback == null)
393 open_callback = new Action<TimeSpan> (OnOpen);
394 return open_callback.BeginInvoke (timeout, callback, state);
397 protected override void OnEndOpen (IAsyncResult result)
399 if (open_callback == null)
400 throw new InvalidOperationException ("Async open operation has not started");
401 open_callback.EndInvoke (result);
404 protected override void OnOpen (TimeSpan timeout)
406 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
407 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
408 if (channel.State == CommunicationState.Created)
409 channel.Open (timeout);
414 IChannel OperationChannel {
415 get { return channel; }
418 public T GetProperty<T> () where T : class
420 if (typeof (T) == typeof (MessageVersion))
421 return (T) (object) message_version;
422 return OperationChannel.GetProperty<T> ();
425 // IExtensibleObject<IContextChannel>
427 IExtensionCollection<IContextChannel> extensions;
429 public IExtensionCollection<IContextChannel> Extensions {
431 if (extensions == null)
432 extensions = new ExtensionCollection<IContextChannel> (this);
437 #region Request/Output processing
439 class OperationParameters
441 public object[] Parameters;
442 public object UserData;
445 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
447 var p = new OperationParameters { Parameters = parameters, UserData = asyncState };
448 return _processDelegate.BeginInvoke (method, operationName, parameters, OperationContext.Current, callback, p);
451 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
454 throw new ArgumentNullException ("result");
455 if (parameters == null)
456 throw new ArgumentNullException ("parameters");
458 var p = (OperationParameters)result.AsyncState;
459 if (p.Parameters.Length != parameters.Length)
460 throw new ArgumentException ("Parameter array has invalid length.", "parameters");
462 for (int i = 0; i < parameters.Length; i++)
463 parameters [i] = p.Parameters [i];
465 return _processDelegate.EndInvoke (result);
468 public object Process (MethodBase method, string operationName, object [] parameters, OperationContext context)
470 var previousContext = OperationContext.Current;
472 // Inherit the context from the calling thread
473 OperationContext.Current = context;
475 return DoProcess (method, operationName, parameters, context);
476 } catch (Exception ex) {
479 // Reset the context before the thread goes back into the pool
480 OperationContext.Current = previousContext;
484 object DoProcess (MethodBase method, string operationName, object [] parameters, OperationContext context)
486 if (AllowInitializationUI)
487 DisplayInitializationUI ();
488 OperationDescription od = SelectOperation (method, operationName, parameters);
490 if (State != CommunicationState.Opened)
494 return Request (od, parameters, context);
496 Output (od, parameters, context);
501 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
504 if (Runtime.OperationSelector != null)
505 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
507 operation = operationName;
508 OperationDescription od = contract.Operations.Find (operation);
510 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
514 void Output (OperationDescription od, object [] parameters, OperationContext context)
516 ClientOperation op = runtime.Operations [od.Name];
517 Send (CreateRequest (op, parameters, context), OperationTimeout);
520 object Request (OperationDescription od, object [] parameters, OperationContext context)
522 ClientOperation op = runtime.Operations [od.Name];
523 object [] inspections = new object [runtime.MessageInspectors.Count];
524 Message req = CreateRequest (op, parameters, context);
526 for (int i = 0; i < inspections.Length; i++)
527 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
529 Message res = Request (req, OperationTimeout);
531 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
532 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
533 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
535 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
536 if (fault.HasDetail) {
537 Type detailType = typeof (ExceptionDetail);
538 var freader = fault.GetReaderAtDetailContents ();
539 DataContractSerializer ds = null;
540 foreach (var fci in op.FaultContractInfos)
541 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
542 detailType = fci.Detail;
547 ds = new DataContractSerializer (detailType);
548 var detail = ds.ReadObject (freader);
549 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
553 ex = new FaultException (fault);
558 for (int i = 0; i < inspections.Length; i++)
559 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
561 if (op.DeserializeReply)
562 return op.Formatter.DeserializeReply (res, parameters);
567 #region Message-based Request() and Send()
568 // They are internal for ClientBase<T>.ChannelBase use.
569 internal Message Request (Message msg, TimeSpan timeout)
571 if (RequestChannel != null)
572 return RequestChannel.Request (msg, timeout);
574 return RequestCorrelated (msg, timeout, OutputChannel);
577 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
579 // FIXME: implement ConcurrencyMode check:
580 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
582 DateTime startTime = DateTime.Now;
583 OutputChannel.Send (msg, timeout);
584 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
587 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
589 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
592 internal Message EndRequest (IAsyncResult result)
594 return requestDelegate.EndInvoke (result);
597 internal void Send (Message msg, TimeSpan timeout)
599 if (OutputChannel != null)
600 OutputChannel.Send (msg, timeout);
602 RequestChannel.Request (msg, timeout); // and ignore returned message.
605 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
607 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
610 internal void EndSend (IAsyncResult result)
612 sendDelegate.EndInvoke (result);
616 Message CreateRequest (ClientOperation op, object [] parameters, OperationContext context)
618 MessageVersion version = message_version;
620 version = MessageVersion.Default;
623 if (op.SerializeRequest)
624 msg = op.Formatter.SerializeRequest (
625 version, parameters);
627 if (parameters.Length != 1)
628 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));
629 if (!(parameters [0] is Message))
630 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
631 msg = (Message) parameters [0];
634 context = context ?? OperationContext.Current;
635 if (context != null) {
636 // CopyHeadersFrom does not work here (brings duplicates -> error)
637 foreach (var mh in context.OutgoingMessageHeaders) {
638 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
640 msg.Headers.RemoveAt (x);
641 msg.Headers.Add ((MessageHeader) mh);
643 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
646 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
647 //if (OutputSession != null)
648 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
649 msg.Properties.AllowOutputBatching = AllowOutputBatching;
651 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
652 if (msg.Headers.MessageId == null)
653 msg.Headers.MessageId = new UniqueId ();
654 if (msg.Headers.ReplyTo == null)
655 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
656 if (msg.Headers.To == null && RemoteAddress != null)
657 msg.Headers.To = RemoteAddress.Uri;