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 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
441 return _processDelegate.BeginInvoke (method, operationName, parameters, OperationContext.Current, callback, asyncState);
444 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
447 throw new ArgumentNullException ("result");
448 if (parameters == null)
449 throw new ArgumentNullException ("parameters");
450 // FIXME: the method arguments should be verified to be
451 // identical to the arguments in the corresponding begin method.
452 return _processDelegate.EndInvoke (result);
455 public object Process (MethodBase method, string operationName, object [] parameters, OperationContext context)
457 var previousContext = OperationContext.Current;
459 // Inherit the context from the calling thread
460 OperationContext.Current = context;
462 return DoProcess (method, operationName, parameters, context);
463 } catch (Exception ex) {
466 // Reset the context before the thread goes back into the pool
467 OperationContext.Current = previousContext;
471 object DoProcess (MethodBase method, string operationName, object [] parameters, OperationContext context)
473 if (AllowInitializationUI)
474 DisplayInitializationUI ();
475 OperationDescription od = SelectOperation (method, operationName, parameters);
477 if (State != CommunicationState.Opened)
481 return Request (od, parameters, context);
483 Output (od, parameters, context);
488 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
491 if (Runtime.OperationSelector != null)
492 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
494 operation = operationName;
495 OperationDescription od = contract.Operations.Find (operation);
497 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
501 void Output (OperationDescription od, object [] parameters, OperationContext context)
503 ClientOperation op = runtime.Operations [od.Name];
504 Send (CreateRequest (op, parameters, context), OperationTimeout);
507 object Request (OperationDescription od, object [] parameters, OperationContext context)
509 ClientOperation op = runtime.Operations [od.Name];
510 object [] inspections = new object [runtime.MessageInspectors.Count];
511 Message req = CreateRequest (op, parameters, context);
513 for (int i = 0; i < inspections.Length; i++)
514 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
516 Message res = Request (req, OperationTimeout);
518 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
519 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
520 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
522 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
523 if (fault.HasDetail) {
524 Type detailType = typeof (ExceptionDetail);
525 var freader = fault.GetReaderAtDetailContents ();
526 DataContractSerializer ds = null;
527 foreach (var fci in op.FaultContractInfos)
528 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
529 detailType = fci.Detail;
534 ds = new DataContractSerializer (detailType);
535 var detail = ds.ReadObject (freader);
536 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
540 ex = new FaultException (fault);
545 for (int i = 0; i < inspections.Length; i++)
546 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
548 if (op.DeserializeReply)
549 return op.Formatter.DeserializeReply (res, parameters);
554 #region Message-based Request() and Send()
555 // They are internal for ClientBase<T>.ChannelBase use.
556 internal Message Request (Message msg, TimeSpan timeout)
558 if (RequestChannel != null)
559 return RequestChannel.Request (msg, timeout);
561 return RequestCorrelated (msg, timeout, OutputChannel);
564 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
566 // FIXME: implement ConcurrencyMode check:
567 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
569 DateTime startTime = DateTime.Now;
570 OutputChannel.Send (msg, timeout);
571 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
574 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
576 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
579 internal Message EndRequest (IAsyncResult result)
581 return requestDelegate.EndInvoke (result);
584 internal void Send (Message msg, TimeSpan timeout)
586 if (OutputChannel != null)
587 OutputChannel.Send (msg, timeout);
589 RequestChannel.Request (msg, timeout); // and ignore returned message.
592 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
594 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
597 internal void EndSend (IAsyncResult result)
599 sendDelegate.EndInvoke (result);
603 Message CreateRequest (ClientOperation op, object [] parameters, OperationContext context)
605 MessageVersion version = message_version;
607 version = MessageVersion.Default;
610 if (op.SerializeRequest)
611 msg = op.Formatter.SerializeRequest (
612 version, parameters);
614 if (parameters.Length != 1)
615 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));
616 if (!(parameters [0] is Message))
617 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
618 msg = (Message) parameters [0];
621 context = context ?? OperationContext.Current;
622 if (context != null) {
623 // CopyHeadersFrom does not work here (brings duplicates -> error)
624 foreach (var mh in context.OutgoingMessageHeaders) {
625 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
627 msg.Headers.RemoveAt (x);
628 msg.Headers.Add ((MessageHeader) mh);
630 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
633 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
634 //if (OutputSession != null)
635 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
636 msg.Properties.AllowOutputBatching = AllowOutputBatching;
638 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
639 if (msg.Headers.MessageId == null)
640 msg.Headers.MessageId = new UniqueId ();
641 if (msg.Headers.ReplyTo == null)
642 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
643 if (msg.Headers.To == null && RemoteAddress != null)
644 msg.Headers.To = RemoteAddress.Uri;