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;
452 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
455 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
459 throw new ArgumentNullException ("result");
460 if (parameters == null)
461 throw new ArgumentNullException ("parameters");
462 // FIXME: the method arguments should be verified to be
463 // identical to the arguments in the corresponding begin method.
464 object asyncResult = _processDelegate.EndInvoke (result);
469 public object Process (MethodBase method, string operationName, object [] parameters)
471 var previousContext = OperationContext.Current;
473 // Inherit the context from the calling thread
474 if (this.context != null)
475 OperationContext.Current = this.context;
477 return DoProcess (method, operationName, parameters);
478 } catch (Exception ex) {
481 // Reset the context before the thread goes back into the pool
482 OperationContext.Current = previousContext;
486 object DoProcess (MethodBase method, string operationName, object [] parameters)
488 if (AllowInitializationUI)
489 DisplayInitializationUI ();
490 OperationDescription od = SelectOperation (method, operationName, parameters);
492 if (State != CommunicationState.Opened)
496 return Request (od, parameters);
498 Output (od, parameters);
503 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
506 if (Runtime.OperationSelector != null)
507 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
509 operation = operationName;
510 OperationDescription od = contract.Operations.Find (operation);
512 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
516 void Output (OperationDescription od, object [] parameters)
518 ClientOperation op = runtime.Operations [od.Name];
519 Send (CreateRequest (op, parameters), OperationTimeout);
522 object Request (OperationDescription od, object [] parameters)
524 ClientOperation op = runtime.Operations [od.Name];
525 object [] inspections = new object [runtime.MessageInspectors.Count];
526 Message req = CreateRequest (op, parameters);
528 for (int i = 0; i < inspections.Length; i++)
529 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
531 Message res = Request (req, OperationTimeout);
533 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
534 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
535 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
537 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
538 if (fault.HasDetail) {
539 Type detailType = typeof (ExceptionDetail);
540 var freader = fault.GetReaderAtDetailContents ();
541 DataContractSerializer ds = null;
542 foreach (var fci in op.FaultContractInfos)
543 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
544 detailType = fci.Detail;
549 ds = new DataContractSerializer (detailType);
550 var detail = ds.ReadObject (freader);
551 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
555 ex = new FaultException (fault);
560 for (int i = 0; i < inspections.Length; i++)
561 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
563 if (op.DeserializeReply)
564 return op.Formatter.DeserializeReply (res, parameters);
569 #region Message-based Request() and Send()
570 // They are internal for ClientBase<T>.ChannelBase use.
571 internal Message Request (Message msg, TimeSpan timeout)
573 if (RequestChannel != null)
574 return RequestChannel.Request (msg, timeout);
576 return RequestCorrelated (msg, timeout, OutputChannel);
579 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
581 // FIXME: implement ConcurrencyMode check:
582 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
584 DateTime startTime = DateTime.Now;
585 OutputChannel.Send (msg, timeout);
586 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
589 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
591 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
594 internal Message EndRequest (IAsyncResult result)
596 return requestDelegate.EndInvoke (result);
599 internal void Send (Message msg, TimeSpan timeout)
601 if (OutputChannel != null)
602 OutputChannel.Send (msg, timeout);
604 RequestChannel.Request (msg, timeout); // and ignore returned message.
607 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
609 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
612 internal void EndSend (IAsyncResult result)
614 sendDelegate.EndInvoke (result);
618 Message CreateRequest (ClientOperation op, object [] parameters)
620 MessageVersion version = message_version;
622 version = MessageVersion.Default;
625 if (op.SerializeRequest)
626 msg = op.Formatter.SerializeRequest (
627 version, parameters);
629 if (parameters.Length != 1)
630 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));
631 if (!(parameters [0] is Message))
632 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
633 msg = (Message) parameters [0];
636 context = context ?? OperationContext.Current;
637 if (context != null) {
638 // CopyHeadersFrom does not work here (brings duplicates -> error)
639 foreach (var mh in context.OutgoingMessageHeaders) {
640 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
642 msg.Headers.RemoveAt (x);
643 msg.Headers.Add ((MessageHeader) mh);
645 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
648 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
649 //if (OutputSession != null)
650 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
651 msg.Properties.AllowOutputBatching = AllowOutputBatching;
653 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
654 if (msg.Headers.MessageId == null)
655 msg.Headers.MessageId = new UniqueId ();
656 if (msg.Headers.ReplyTo == null)
657 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
658 if (msg.Headers.To == null && RemoteAddress != null)
659 msg.Headers.To = RemoteAddress.Uri;