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 if (State != CommunicationState.Opened)
443 return Request (od, parameters);
445 Output (od, parameters);
450 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
453 if (Runtime.OperationSelector != null)
454 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
456 operation = operationName;
457 OperationDescription od = contract.Operations.Find (operation);
459 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
463 void Output (OperationDescription od, object [] parameters)
465 ClientOperation op = runtime.Operations [od.Name];
466 Send (CreateRequest (op, parameters), OperationTimeout);
469 object Request (OperationDescription od, object [] parameters)
471 ClientOperation op = runtime.Operations [od.Name];
472 object [] inspections = new object [runtime.MessageInspectors.Count];
473 Message req = CreateRequest (op, parameters);
475 for (int i = 0; i < inspections.Length; i++)
476 inspections [i] = runtime.MessageInspectors [i].BeforeSendRequest (ref req, this);
478 Message res = Request (req, OperationTimeout);
480 MessageFault fault = MessageFault.CreateFault (res, runtime.MaxFaultSize);
481 if (fault.HasDetail && fault is MessageFault.SimpleMessageFault) {
482 MessageFault.SimpleMessageFault simpleFault = fault as MessageFault.SimpleMessageFault;
483 object detail = simpleFault.Detail;
484 Type t = detail.GetType ();
485 Type faultType = typeof (FaultException<>).MakeGenericType (t);
486 object [] constructorParams = new object [] { detail, fault.Reason, fault.Code, fault.Actor };
487 FaultException fe = (FaultException) Activator.CreateInstance (faultType, constructorParams);
491 // given a MessageFault, it is hard to figure out the type of the embedded detail
492 throw new FaultException(fault);
496 for (int i = 0; i < inspections.Length; i++)
497 runtime.MessageInspectors [i].AfterReceiveReply (ref res, inspections [i]);
499 if (op.DeserializeReply)
500 return op.GetFormatter ().DeserializeReply (res, parameters);
505 #region Message-based Request() and Send()
506 // They are internal for ClientBase<T>.ChannelBase use.
507 internal Message Request (Message msg, TimeSpan timeout)
509 if (RequestChannel != null)
510 return RequestChannel.Request (msg, timeout);
512 DateTime startTime = DateTime.Now;
513 OutputChannel.Send (msg, timeout);
514 return ((IDuplexChannel) OutputChannel).Receive (timeout - (DateTime.Now - startTime));
518 internal IAsyncResult BeginRequest (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
520 return requestDelegate.BeginInvoke (msg, timeout, callback, state);
523 internal Message EndRequest (IAsyncResult result)
525 return requestDelegate.EndInvoke (result);
528 internal void Send (Message msg, TimeSpan timeout)
530 OutputChannel.Send (msg, timeout);
533 internal IAsyncResult BeginSend (Message msg, TimeSpan timeout, AsyncCallback callback, object state)
535 return sendDelegate.BeginInvoke (msg, timeout, callback, state);
538 internal void EndSend (IAsyncResult result)
540 sendDelegate.EndInvoke (result);
544 Message CreateRequest (ClientOperation op, object [] parameters)
546 MessageVersion version = message_version;
548 version = MessageVersion.Default;
551 if (op.SerializeRequest)
552 msg = op.GetFormatter ().SerializeRequest (
553 version, parameters);
555 if (parameters.Length != 1)
556 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));
557 if (!(parameters [0] is Message))
558 throw new ArgumentException (String.Format ("Argument should be only a Message, but has {0}", parameters [0] != null ? parameters [0].GetType ().FullName : "null"));
559 msg = (Message) parameters [0];
562 context = context ?? OperationContext.Current;
563 if (context != null) {
564 // CopyHeadersFrom does not work here (brings duplicates -> error)
565 foreach (var mh in context.OutgoingMessageHeaders) {
566 int x = msg.Headers.FindHeader (mh.Name, mh.Namespace, mh.Actor);
568 msg.Headers.RemoveAt (x);
569 msg.Headers.Add ((MessageHeader) mh);
571 msg.Properties.CopyProperties (context.OutgoingMessageProperties);
574 if (OutputSession != null)
575 msg.Headers.MessageId = new UniqueId (OutputSession.Id);
576 msg.Properties.AllowOutputBatching = AllowOutputBatching;