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.Reflection;
30 using System.ServiceModel.Channels;
31 using System.ServiceModel.Description;
32 using System.ServiceModel.Dispatcher;
33 using System.ServiceModel.Security;
34 using System.Threading;
37 namespace System.ServiceModel
39 internal class ClientRuntimeChannel
40 : CommunicationObject, IClientChannel
42 ClientRuntime runtime;
43 EndpointAddress remote_address;
44 ContractDescription contract;
45 MessageVersion message_version;
46 TimeSpan default_open_timeout, default_close_timeout;
48 IChannelFactory factory;
49 OperationContext context;
52 readonly ProcessDelegate _processDelegate;
54 delegate object ProcessDelegate (MethodBase method, string operationName, object [] parameters);
56 readonly RequestDelegate requestDelegate;
58 delegate Message RequestDelegate (Message msg, TimeSpan timeout);
60 readonly SendDelegate sendDelegate;
62 delegate void SendDelegate (Message msg, TimeSpan timeout);
65 public ClientRuntimeChannel (ServiceEndpoint endpoint,
66 ChannelFactory channelFactory, EndpointAddress remoteAddress, Uri via)
67 : this (endpoint.CreateRuntime (), endpoint.Contract, channelFactory.DefaultOpenTimeout, channelFactory.DefaultCloseTimeout, null, channelFactory.OpenedChannelFactory, endpoint.Binding.MessageVersion, remoteAddress, via)
71 public ClientRuntimeChannel (ClientRuntime runtime, ContractDescription contract, TimeSpan openTimeout, TimeSpan closeTimeout, IChannel contextChannel, IChannelFactory factory, MessageVersion messageVersion, EndpointAddress remoteAddress, Uri via)
73 this.runtime = runtime;
74 this.remote_address = remoteAddress;
76 this.contract = contract;
77 this.message_version = messageVersion;
78 default_open_timeout = openTimeout;
79 default_close_timeout = closeTimeout;
80 _processDelegate = new ProcessDelegate (Process);
81 requestDelegate = new RequestDelegate (Request);
82 sendDelegate = new SendDelegate (Send);
85 AllowInitializationUI = true;
86 OperationTimeout = TimeSpan.FromMinutes (1);
88 if (contextChannel != null)
89 channel = contextChannel;
91 var method = factory.GetType ().GetMethod ("CreateChannel", new Type [] {typeof (EndpointAddress), typeof (Uri)});
92 channel = (IChannel) method.Invoke (factory, new object [] {remote_address, Via});
93 this.factory = factory;
97 public ClientRuntime Runtime {
98 get { return runtime; }
101 IRequestChannel RequestChannel {
102 get { return channel as IRequestChannel; }
105 IOutputChannel OutputChannel {
106 get { return channel as IOutputChannel; }
109 internal IDuplexChannel DuplexChannel {
110 get { return channel as IDuplexChannel; }
113 #region IClientChannel
115 bool did_interactive_initialization;
117 public bool AllowInitializationUI { get; set; }
119 public bool DidInteractiveInitialization {
120 get { return did_interactive_initialization; }
124 get { return runtime.Via; }
127 class DelegatingWaitHandle : WaitHandle
129 public DelegatingWaitHandle (IAsyncResult [] results)
131 this.results = results;
134 IAsyncResult [] results;
136 protected override void Dispose (bool disposing)
139 foreach (var r in results)
140 r.AsyncWaitHandle.Close ();
143 public override bool WaitOne ()
145 foreach (var r in results)
146 r.AsyncWaitHandle.WaitOne ();
150 public override bool WaitOne (int millisecondsTimeout)
152 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
155 WaitHandle [] ResultWaitHandles {
157 var arr = new WaitHandle [results.Length];
158 for (int i = 0; i < arr.Length; i++)
159 arr [i] = results [i].AsyncWaitHandle;
164 #if !NET_2_1 || MONOTOUCH
165 public override bool WaitOne (int millisecondsTimeout, bool exitContext)
167 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout, exitContext);
170 public override bool WaitOne (TimeSpan timeout, bool exitContext)
172 return WaitHandle.WaitAll (ResultWaitHandles, timeout, exitContext);
177 class DisplayUIAsyncResult : IAsyncResult
179 public DisplayUIAsyncResult (IAsyncResult [] results)
181 this.results = results;
184 IAsyncResult [] results;
186 internal IAsyncResult [] Results {
187 get { return results; }
190 public object AsyncState {
194 WaitHandle wait_handle;
196 public WaitHandle AsyncWaitHandle {
198 if (wait_handle == null)
199 wait_handle = new DelegatingWaitHandle (results);
204 public bool CompletedSynchronously {
206 foreach (var r in results)
207 if (!r.CompletedSynchronously)
212 public bool IsCompleted {
214 foreach (var r in results)
222 public IAsyncResult BeginDisplayInitializationUI (
223 AsyncCallback callback, object state)
225 OnInitializationUI ();
226 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
228 foreach (var init in runtime.InteractiveChannelInitializers)
229 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
230 return new DisplayUIAsyncResult (arr);
233 public void EndDisplayInitializationUI (
236 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
238 foreach (var init in runtime.InteractiveChannelInitializers)
239 init.EndDisplayInitializationUI (r.Results [i++]);
241 did_interactive_initialization = true;
244 public void DisplayInitializationUI ()
246 OnInitializationUI ();
247 foreach (var init in runtime.InteractiveChannelInitializers)
248 init.EndDisplayInitializationUI (init.BeginDisplayInitializationUI (this, null, null));
250 did_interactive_initialization = true;
253 void OnInitializationUI ()
255 if (!AllowInitializationUI && runtime.InteractiveChannelInitializers.Count > 0)
256 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
259 public void Dispose ()
264 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
268 #region IContextChannel
271 public bool AllowOutputBatching { get; set; }
273 public IInputSession InputSession {
275 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
276 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
279 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
280 return dch != null ? dch.Session : null;
284 public EndpointAddress LocalAddress {
286 var dc = OperationChannel as IDuplexChannel;
287 return dc != null ? dc.LocalAddress : null;
292 public TimeSpan OperationTimeout { get; set; }
294 public IOutputSession OutputSession {
296 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
297 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
300 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
301 return dch != null ? dch.Session : null;
305 public EndpointAddress RemoteAddress {
306 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
309 public string SessionId {
310 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
315 // CommunicationObject
316 protected internal override TimeSpan DefaultOpenTimeout {
317 get { return default_open_timeout; }
320 protected internal override TimeSpan DefaultCloseTimeout {
321 get { return default_close_timeout; }
324 protected override void OnAbort ()
327 if (factory != null) // ... is it valid?
331 Action<TimeSpan> close_delegate;
333 protected override IAsyncResult OnBeginClose (
334 TimeSpan timeout, AsyncCallback callback, object state)
336 if (close_delegate == null)
337 close_delegate = new Action<TimeSpan> (OnClose);
338 return close_delegate.BeginInvoke (timeout, callback, state);
341 protected override void OnEndClose (IAsyncResult result)
343 close_delegate.EndInvoke (result);
346 protected override void OnClose (TimeSpan timeout)
348 DateTime start = DateTime.Now;
349 channel.Close (timeout);
352 Action<TimeSpan> open_callback;
354 protected override IAsyncResult OnBeginOpen (
355 TimeSpan timeout, AsyncCallback callback, object state)
357 if (open_callback == null)
358 open_callback = new Action<TimeSpan> (OnOpen);
359 return open_callback.BeginInvoke (timeout, callback, state);
362 protected override void OnEndOpen (IAsyncResult result)
364 if (open_callback == null)
365 throw new InvalidOperationException ("Async open operation has not started");
366 open_callback.EndInvoke (result);
369 protected override void OnOpen (TimeSpan timeout)
371 if (runtime.InteractiveChannelInitializers.Count > 0 && !DidInteractiveInitialization)
372 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
373 if (channel.State == CommunicationState.Created)
374 channel.Open (timeout);
379 IChannel OperationChannel {
380 get { return channel; }
383 public T GetProperty<T> () where T : class
385 return OperationChannel.GetProperty<T> ();
388 // IExtensibleObject<IContextChannel>
390 IExtensionCollection<IContextChannel> extensions;
392 public IExtensionCollection<IContextChannel> Extensions {
394 if (extensions == null)
395 extensions = new ExtensionCollection<IContextChannel> (this);
400 #region Request/Output processing
402 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
405 throw new InvalidOperationException ("another operation is in progress");
406 context = OperationContext.Current;
407 return _processDelegate.BeginInvoke (method, operationName, parameters, callback, asyncState);
410 public object EndProcess (MethodBase method, string operationName, object [] parameters, IAsyncResult result)
414 throw new ArgumentNullException ("result");
415 if (parameters == null)
416 throw new ArgumentNullException ("parameters");
417 // FIXME: the method arguments should be verified to be
418 // identical to the arguments in the corresponding begin method.
419 return _processDelegate.EndInvoke (result);
422 public object Process (MethodBase method, string operationName, object [] parameters)
425 return DoProcess (method, operationName, parameters);
426 } catch (Exception ex) {
427 Console.Write ("Exception in async operation: ");
428 Console.WriteLine (ex);
433 object DoProcess (MethodBase method, string operationName, object [] parameters)
435 if (AllowInitializationUI)
436 DisplayInitializationUI ();
437 OperationDescription od = SelectOperation (method, operationName, parameters);
439 return Request (od, parameters);
441 Output (od, parameters);
446 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
449 if (Runtime.OperationSelector != null)
450 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
452 operation = operationName;
453 OperationDescription od = contract.Operations.Find (operation);
455 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
459 void Output (OperationDescription od, object [] parameters)
461 if (OutputChannel.State != CommunicationState.Opened)
462 OutputChannel.Open ();
464 ClientOperation op = runtime.Operations [od.Name];
465 Send (CreateRequest (op, parameters), OperationTimeout);
468 object Request (OperationDescription od, object [] parameters)
470 if (OperationChannel.State != CommunicationState.Opened)
471 OperationChannel.Open ();
473 ClientOperation op = runtime.Operations [od.Name];
474 object [] inspections = new object [runtime.MessageInspectors.Count];
475 Message req = CreateRequest (op, parameters);
477 for (int i = 0; i < inspections.Length; i++)
478 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
480 Message res = Request (req, OperationTimeout);
482 MessageFault fault = MessageFault.CreateFault (res, runtime.MaxFaultSize);
483 if (fault.HasDetail && fault is MessageFault.SimpleMessageFault) {
484 MessageFault.SimpleMessageFault simpleFault = fault as MessageFault.SimpleMessageFault;
485 object detail = simpleFault.Detail;
486 Type t = detail.GetType ();
487 Type faultType = typeof (FaultException<>).MakeGenericType (t);
488 object [] constructorParams = new object [] { detail, fault.Reason, fault.Code, fault.Actor };
489 FaultException fe = (FaultException) Activator.CreateInstance (faultType, constructorParams);
493 // given a MessageFault, it is hard to figure out the type of the embedded detail
494 throw new FaultException(fault);
498 for (int i = 0; i < inspections.Length; i++)
499 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
501 if (op.DeserializeReply)
502 return op.GetFormatter ().DeserializeReply (res, parameters);
507 #region Message-based Request() and Send()
508 // They are internal for ClientBase<T>.ChannelBase use.
509 internal Message Request (Message msg, TimeSpan timeout)
511 if (RequestChannel != null)
512 return RequestChannel.Request (msg, timeout);
514 DateTime startTime = DateTime.Now;
515 OutputChannel.Send (msg, timeout);
516 return ((IDuplexChannel) OutputChannel).Receive (timeout - (DateTime.Now - startTime));
520 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
522 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
525 internal Message EndRequest (IAsyncResult result)
527 return requestDelegate.EndInvoke (result);
530 internal void Send (Message msg, TimeSpan timeout)
532 OutputChannel.Send (msg, timeout);
535 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
537 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
540 internal void EndSend (IAsyncResult result)
542 sendDelegate.EndInvoke (result);
546 Message CreateRequest (ClientOperation op, object [] parameters)
548 MessageVersion version = message_version;
550 version = MessageVersion.Default;
553 if (op.SerializeRequest)
554 msg = op.GetFormatter ().SerializeRequest (
555 version, parameters);
557 msg = (Message) parameters [0];
559 context = context ?? OperationContext.Current;
560 if (context != null) {
561 // CopyHeadersFrom does not work here (brings duplicates -> error)
562 foreach (var mh in context.OutgoingMessageHeaders) {
563 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
565 msg.Headers.RemoveAt (x);
566 msg.Headers.Add ((MessageHeader) mh);
568 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
571 if (OutputSession != null)
572 msg.Headers.MessageId = new UniqueId (OutputSession.Id);
573 msg.Properties.AllowOutputBatching = AllowOutputBatching;