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 this.runtime = runtime;
97 this.remote_address = remoteAddress;
98 if (runtime.Via == null)
99 runtime.Via = via ?? (remote_address != null ?remote_address.Uri : null);
100 this.contract = contract;
101 this.message_version = messageVersion;
102 default_open_timeout = openTimeout;
103 default_close_timeout = closeTimeout;
104 _processDelegate = new ProcessDelegate (Process);
105 requestDelegate = new RequestDelegate (Request);
106 sendDelegate = new SendDelegate (Send);
109 AllowInitializationUI = true;
110 OperationTimeout = TimeSpan.FromMinutes (1);
112 if (contextChannel != null)
113 channel = contextChannel;
115 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
117 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
118 this.factory = factory;
119 } catch (TargetInvocationException ex) {
120 if (ex.InnerException != null)
121 throw ex.InnerException;
128 public ContractDescription Contract {
129 get { return contract; }
132 public ClientRuntime Runtime {
133 get { return runtime; }
136 IRequestChannel RequestChannel {
137 get { return channel as IRequestChannel; }
140 IOutputChannel OutputChannel {
141 get { return channel as IOutputChannel; }
144 internal IDuplexChannel DuplexChannel {
145 get { return channel as IDuplexChannel; }
148 #region IClientChannel
150 bool did_interactive_initialization;
152 public bool AllowInitializationUI { get; set; }
154 public bool DidInteractiveInitialization {
155 get { return did_interactive_initialization; }
159 get { return runtime.Via; }
162 class DelegatingWaitHandle : WaitHandle
164 public DelegatingWaitHandle (IAsyncResult [] results)
166 this.results = results;
169 IAsyncResult [] results;
171 protected override void Dispose (bool disposing)
174 foreach (var r in results)
175 r.AsyncWaitHandle.Close ();
178 public override bool WaitOne ()
180 foreach (var r in results)
181 r.AsyncWaitHandle.WaitOne ();
185 public override bool WaitOne (int millisecondsTimeout)
187 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
190 WaitHandle [] ResultWaitHandles {
192 var arr = new WaitHandle [results.Length];
193 for (int i = 0; i < arr.Length; i++)
194 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);
212 class DisplayUIAsyncResult : IAsyncResult
214 public DisplayUIAsyncResult (IAsyncResult [] results)
216 this.results = results;
219 IAsyncResult [] results;
221 internal IAsyncResult [] Results {
222 get { return results; }
225 public object AsyncState {
229 WaitHandle wait_handle;
231 public WaitHandle AsyncWaitHandle {
233 if (wait_handle == null)
234 wait_handle = new DelegatingWaitHandle (results);
239 public bool CompletedSynchronously {
241 foreach (var r in results)
242 if (!r.CompletedSynchronously)
247 public bool IsCompleted {
249 foreach (var r in results)
257 public IAsyncResult BeginDisplayInitializationUI (
258 AsyncCallback callback, object state)
260 OnInitializationUI ();
261 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
263 foreach (var init in runtime.InteractiveChannelInitializers)
264 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
265 return new DisplayUIAsyncResult (arr);
268 public void EndDisplayInitializationUI (
271 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
273 foreach (var init in runtime.InteractiveChannelInitializers)
274 init.EndDisplayInitializationUI (r.Results [i++]);
276 did_interactive_initialization = true;
279 public void DisplayInitializationUI ()
281 OnInitializationUI ();
282 foreach (var init in runtime.InteractiveChannelInitializers)
283 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
285 did_interactive_initialization = true;
288 void OnInitializationUI ()
290 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
291 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
294 public void Dispose ()
299 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
303 #region IContextChannel
306 public bool AllowOutputBatching { get; set; }
308 public IInputSession InputSession {
310 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
311 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
314 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
315 return dch != null ? dch.Session : null;
319 public EndpointAddress LocalAddress {
321 var dc = OperationChannel as IDuplexChannel;
322 return dc != null ? dc.LocalAddress : null;
327 public TimeSpan OperationTimeout { get; set; }
329 public IOutputSession OutputSession {
331 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
332 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
335 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
336 return dch != null ? dch.Session : null;
340 public EndpointAddress RemoteAddress {
341 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
344 public string SessionId {
345 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
350 // CommunicationObject
351 protected internal override TimeSpan DefaultOpenTimeout {
352 get { return default_open_timeout; }
355 protected internal override TimeSpan DefaultCloseTimeout {
356 get { return default_close_timeout; }
359 protected override void OnAbort ()
362 if (factory != null) // ... is it valid?
366 Action<TimeSpan> close_delegate;
368 protected override IAsyncResult OnBeginClose (
369 TimeSpan timeout, AsyncCallback callback, object state)
371 if (close_delegate == null)
372 close_delegate = new Action<TimeSpan> (OnClose);
373 return close_delegate.BeginInvoke (timeout, callback, state);
376 protected override void OnEndClose (IAsyncResult result)
378 close_delegate.EndInvoke (result);
381 protected override void OnClose (TimeSpan timeout)
383 DateTime start = DateTime.Now;
384 if (channel.State == CommunicationState.Opened)
385 channel.Close (timeout);
388 Action<TimeSpan> open_callback;
390 protected override IAsyncResult OnBeginOpen (
391 TimeSpan timeout, AsyncCallback callback, object state)
393 if (open_callback == null)
394 open_callback = new Action<TimeSpan> (OnOpen);
395 return open_callback.BeginInvoke (timeout, callback, state);
398 protected override void OnEndOpen (IAsyncResult result)
400 if (open_callback == null)
401 throw new InvalidOperationException ("Async open operation has not started");
402 open_callback.EndInvoke (result);
405 protected override void OnOpen (TimeSpan timeout)
407 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
408 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
409 if (channel.State == CommunicationState.Created)
410 channel.Open (timeout);
415 IChannel OperationChannel {
416 get { return channel; }
419 public T GetProperty<T> () where T : class
421 return OperationChannel.GetProperty<T> ();
424 // IExtensibleObject<IContextChannel>
426 IExtensionCollection<IContextChannel> extensions;
428 public IExtensionCollection<IContextChannel> Extensions {
430 if (extensions == null)
431 extensions = new ExtensionCollection<IContextChannel> (this);
436 #region Request/Output processing
438 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
441 throw new InvalidOperationException ("another operation is in progress");
442 context = OperationContext.Current;
444 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
447 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
451 throw new ArgumentNullException ("result");
452 if (parameters == null)
453 throw new ArgumentNullException ("parameters");
454 // FIXME: the method arguments should be verified to be
455 // identical to the arguments in the corresponding begin method.
456 return _processDelegate.EndInvoke (result);
459 public object Process (MethodBase method, string operationName, object [] parameters)
462 return DoProcess (method, operationName, parameters);
463 } catch (Exception ex) {
464 #if MOONLIGHT // just for debugging
465 Console.Write ("Exception in async operation: ");
466 Console.WriteLine (ex);
472 object DoProcess (MethodBase method, string operationName, object [] parameters)
474 if (AllowInitializationUI)
475 DisplayInitializationUI ();
476 OperationDescription od = SelectOperation (method, operationName, parameters);
478 if (State != CommunicationState.Opened)
482 return Request (od, parameters);
484 Output (od, parameters);
489 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
492 if (Runtime.OperationSelector != null)
493 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
495 operation = operationName;
496 OperationDescription od = contract.Operations.Find (operation);
498 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
502 void Output (OperationDescription od, object [] parameters)
504 ClientOperation op = runtime.Operations [od.Name];
505 Send (CreateRequest (op, parameters), OperationTimeout);
508 object Request (OperationDescription od, object [] parameters)
510 ClientOperation op = runtime.Operations [od.Name];
511 object [] inspections = new object [runtime.MessageInspectors.Count];
512 Message req = CreateRequest (op, parameters);
514 for (int i = 0; i < inspections.Length; i++)
515 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
517 Message res = Request (req, OperationTimeout);
519 var resb = res.CreateBufferedCopy (runtime.MaxFaultSize);
520 MessageFault fault = MessageFault.CreateFault (resb.CreateMessage (), runtime.MaxFaultSize);
521 var conv = OperationChannel.GetProperty<FaultConverter> () ?? FaultConverter.GetDefaultFaultConverter (res.Version);
523 if (!conv.TryCreateException (resb.CreateMessage (), fault, out ex)) {
524 if (fault.HasDetail) {
525 Type detailType = typeof (ExceptionDetail);
526 var freader = fault.GetReaderAtDetailContents ();
527 DataContractSerializer ds = null;
529 foreach (var fci in op.FaultContractInfos)
530 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
531 detailType = fci.Detail;
537 ds = new DataContractSerializer (detailType);
538 var detail = ds.ReadObject (freader);
539 ex = (Exception) Activator.CreateInstance (typeof (FaultException<>).MakeGenericType (detailType), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action});
543 ex = new FaultException (fault);
548 for (int i = 0; i < inspections.Length; i++)
549 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
551 if (op.DeserializeReply)
552 return op.Formatter.DeserializeReply (res, parameters);
557 #region Message-based Request() and Send()
558 // They are internal for ClientBase<T>.ChannelBase use.
559 internal Message Request (Message msg, TimeSpan timeout)
561 if (RequestChannel != null)
562 return RequestChannel.Request (msg, timeout);
564 return RequestCorrelated (msg, timeout, OutputChannel);
567 internal virtual Message RequestCorrelated (Message msg, TimeSpan timeout, IOutputChannel channel)
569 // FIXME: implement ConcurrencyMode check:
570 // if it is .Single && this instance for a callback channel && the operation is invoked inside service operation, then error.
572 DateTime startTime = DateTime.Now;
573 OutputChannel.Send (msg, timeout);
574 return ((IDuplexChannel) channel).Receive (timeout - (DateTime.Now - startTime));
577 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
579 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
582 internal Message EndRequest (IAsyncResult result)
584 return requestDelegate.EndInvoke (result);
587 internal void Send (Message msg, TimeSpan timeout)
589 if (OutputChannel != null)
590 OutputChannel.Send (msg, timeout);
592 RequestChannel.Request (msg, timeout); // and ignore returned message.
595 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
597 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
600 internal void EndSend (IAsyncResult result)
602 sendDelegate.EndInvoke (result);
606 Message CreateRequest (ClientOperation op, object [] parameters)
608 MessageVersion version = message_version;
610 version = MessageVersion.Default;
613 if (op.SerializeRequest)
614 msg = op.Formatter.SerializeRequest (
615 version, parameters);
617 if (parameters.Length != 1)
618 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));
619 if (!(parameters [0] is Message))
620 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
621 msg = (Message) parameters [0];
624 context = context ?? OperationContext.Current;
625 if (context != null) {
626 // CopyHeadersFrom does not work here (brings duplicates -> error)
627 foreach (var mh in context.OutgoingMessageHeaders) {
628 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
630 msg.Headers.RemoveAt (x);
631 msg.Headers.Add ((MessageHeader) mh);
633 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
636 // FIXME: disabling MessageId as it's not seen for bug #567672 case. But might be required for PeerDuplexChannel. Check it later.
637 //if (OutputSession != null)
638 // msg.Headers.MessageId = new UniqueId (OutputSession.Id);
639 msg.Properties.AllowOutputBatching = AllowOutputBatching;
641 if (msg.Version.Addressing.Equals (AddressingVersion.WSAddressing10)) {
642 if (msg.Headers.MessageId == null)
643 msg.Headers.MessageId = new UniqueId ();
644 if (msg.Headers.ReplyTo == null)
645 msg.Headers.ReplyTo = new EndpointAddress (Constants.WsaAnonymousUri);
646 if (msg.Headers.To == null && RemoteAddress != null)
647 msg.Headers.To = RemoteAddress.Uri;