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
.Runtime
.Serialization
;
31 using System
.ServiceModel
.Channels
;
32 using System
.ServiceModel
.Description
;
33 using System
.ServiceModel
.Dispatcher
;
34 using System
.ServiceModel
.Security
;
35 using System
.Threading
;
38 namespace System
.ServiceModel
.MonoInternal
40 // FIXME: This is a quick workaround for bug #571907
41 public class ClientRuntimeChannel
42 : CommunicationObject
, IClientChannel
44 ClientRuntime runtime
;
45 EndpointAddress remote_address
;
46 ContractDescription contract
;
47 MessageVersion message_version
;
48 TimeSpan default_open_timeout
, default_close_timeout
;
50 IChannelFactory factory
;
51 OperationContext context
;
54 readonly ProcessDelegate _processDelegate
;
56 delegate object ProcessDelegate (MethodBase method
, string operationName
, object [] parameters
);
58 readonly RequestDelegate requestDelegate
;
60 delegate Message
RequestDelegate (Message msg
, TimeSpan timeout
);
62 readonly SendDelegate sendDelegate
;
64 delegate void SendDelegate (Message msg
, TimeSpan timeout
);
67 public ClientRuntimeChannel (ServiceEndpoint endpoint
,
68 ChannelFactory channelFactory
, EndpointAddress remoteAddress
, Uri via
)
69 : this (endpoint
.CreateRuntime (), endpoint
.Contract
, channelFactory
.DefaultOpenTimeout
, channelFactory
.DefaultCloseTimeout
, null, channelFactory
.OpenedChannelFactory
, endpoint
.Binding
.MessageVersion
, remoteAddress
, via
)
73 public ClientRuntimeChannel (ClientRuntime runtime
, ContractDescription contract
, TimeSpan openTimeout
, TimeSpan closeTimeout
, IChannel contextChannel
, IChannelFactory factory
, MessageVersion messageVersion
, EndpointAddress remoteAddress
, Uri via
)
75 this.runtime
= runtime
;
76 this.remote_address
= remoteAddress
;
78 this.contract
= contract
;
79 this.message_version
= messageVersion
;
80 default_open_timeout
= openTimeout
;
81 default_close_timeout
= closeTimeout
;
82 _processDelegate
= new ProcessDelegate (Process
);
83 requestDelegate
= new RequestDelegate (Request
);
84 sendDelegate
= new SendDelegate (Send
);
87 AllowInitializationUI
= true;
88 OperationTimeout
= TimeSpan
.FromMinutes (1);
90 if (contextChannel
!= null)
91 channel
= contextChannel
;
93 var method
= factory
.GetType ().GetMethod ("CreateChannel", new Type
[] {typeof (EndpointAddress), typeof (Uri)}
);
94 channel
= (IChannel
) method
.Invoke (factory
, new object [] {remote_address, Via}
);
95 this.factory
= factory
;
99 public ContractDescription Contract
{
100 get { return contract; }
103 public ClientRuntime Runtime
{
104 get { return runtime; }
107 IRequestChannel RequestChannel
{
108 get { return channel as IRequestChannel; }
111 IOutputChannel OutputChannel
{
112 get { return channel as IOutputChannel; }
115 internal IDuplexChannel DuplexChannel
{
116 get { return channel as IDuplexChannel; }
119 #region IClientChannel
121 bool did_interactive_initialization
;
123 public bool AllowInitializationUI { get; set; }
125 public bool DidInteractiveInitialization
{
126 get { return did_interactive_initialization; }
130 get { return runtime.Via; }
133 class DelegatingWaitHandle
: WaitHandle
135 public DelegatingWaitHandle (IAsyncResult
[] results
)
137 this.results
= results
;
140 IAsyncResult
[] results
;
142 protected override void Dispose (bool disposing
)
145 foreach (var r
in results
)
146 r
.AsyncWaitHandle
.Close ();
149 public override bool WaitOne ()
151 foreach (var r
in results
)
152 r
.AsyncWaitHandle
.WaitOne ();
156 public override bool WaitOne (int millisecondsTimeout
)
158 return WaitHandle
.WaitAll (ResultWaitHandles
, millisecondsTimeout
);
161 WaitHandle
[] ResultWaitHandles
{
163 var arr
= new WaitHandle
[results
.Length
];
164 for (int i
= 0; i
< arr
.Length
; i
++)
165 arr
[i
] = results
[i
].AsyncWaitHandle
;
171 public override bool WaitOne (int millisecondsTimeout
, bool exitContext
)
173 return WaitHandle
.WaitAll (ResultWaitHandles
, millisecondsTimeout
, exitContext
);
176 public override bool WaitOne (TimeSpan timeout
, bool exitContext
)
178 return WaitHandle
.WaitAll (ResultWaitHandles
, timeout
, exitContext
);
183 class DisplayUIAsyncResult
: IAsyncResult
185 public DisplayUIAsyncResult (IAsyncResult
[] results
)
187 this.results
= results
;
190 IAsyncResult
[] results
;
192 internal IAsyncResult
[] Results
{
193 get { return results; }
196 public object AsyncState
{
200 WaitHandle wait_handle
;
202 public WaitHandle AsyncWaitHandle
{
204 if (wait_handle
== null)
205 wait_handle
= new DelegatingWaitHandle (results
);
210 public bool CompletedSynchronously
{
212 foreach (var r
in results
)
213 if (!r
.CompletedSynchronously
)
218 public bool IsCompleted
{
220 foreach (var r
in results
)
228 public IAsyncResult
BeginDisplayInitializationUI (
229 AsyncCallback callback
, object state
)
231 OnInitializationUI ();
232 IAsyncResult
[] arr
= new IAsyncResult
[runtime
.InteractiveChannelInitializers
.Count
];
234 foreach (var init
in runtime
.InteractiveChannelInitializers
)
235 arr
[i
++] = init
.BeginDisplayInitializationUI (this, callback
, state
);
236 return new DisplayUIAsyncResult (arr
);
239 public void EndDisplayInitializationUI (
242 DisplayUIAsyncResult r
= (DisplayUIAsyncResult
) result
;
244 foreach (var init
in runtime
.InteractiveChannelInitializers
)
245 init
.EndDisplayInitializationUI (r
.Results
[i
++]);
247 did_interactive_initialization
= true;
250 public void DisplayInitializationUI ()
252 OnInitializationUI ();
253 foreach (var init
in runtime
.InteractiveChannelInitializers
)
254 init
.EndDisplayInitializationUI (init
.BeginDisplayInitializationUI (this, null, null));
256 did_interactive_initialization
= true;
259 void OnInitializationUI ()
261 if (!AllowInitializationUI
&& runtime
.InteractiveChannelInitializers
.Count
> 0)
262 throw new InvalidOperationException ("AllowInitializationUI is set to false but the client runtime contains one or more InteractiveChannelInitializers.");
265 public void Dispose ()
270 public event EventHandler
<UnknownMessageReceivedEventArgs
> UnknownMessageReceived
;
274 #region IContextChannel
277 public bool AllowOutputBatching { get; set; }
279 public IInputSession InputSession
{
281 ISessionChannel
<IInputSession
> ch
= RequestChannel
as ISessionChannel
<IInputSession
>;
282 ch
= ch
?? OutputChannel
as ISessionChannel
<IInputSession
>;
285 var dch
= OutputChannel
as ISessionChannel
<IDuplexSession
>;
286 return dch
!= null ? dch
.Session
: null;
290 public EndpointAddress LocalAddress
{
292 var dc
= OperationChannel
as IDuplexChannel
;
293 return dc
!= null ? dc
.LocalAddress
: null;
298 public TimeSpan OperationTimeout { get; set; }
300 public IOutputSession OutputSession
{
302 ISessionChannel
<IOutputSession
> ch
= RequestChannel
as ISessionChannel
<IOutputSession
>;
303 ch
= ch
?? OutputChannel
as ISessionChannel
<IOutputSession
>;
306 var dch
= OutputChannel
as ISessionChannel
<IDuplexSession
>;
307 return dch
!= null ? dch
.Session
: null;
311 public EndpointAddress RemoteAddress
{
312 get { return RequestChannel != null ? RequestChannel.RemoteAddress : OutputChannel.RemoteAddress; }
315 public string SessionId
{
316 get { return OutputSession != null ? OutputSession.Id : InputSession != null ? InputSession.Id : null; }
321 // CommunicationObject
322 protected internal override TimeSpan DefaultOpenTimeout
{
323 get { return default_open_timeout; }
326 protected internal override TimeSpan DefaultCloseTimeout
{
327 get { return default_close_timeout; }
330 protected override void OnAbort ()
333 if (factory
!= null) // ... is it valid?
337 Action
<TimeSpan
> close_delegate
;
339 protected override IAsyncResult
OnBeginClose (
340 TimeSpan timeout
, AsyncCallback callback
, object state
)
342 if (close_delegate
== null)
343 close_delegate
= new Action
<TimeSpan
> (OnClose
);
344 return close_delegate
.BeginInvoke (timeout
, callback
, state
);
347 protected override void OnEndClose (IAsyncResult result
)
349 close_delegate
.EndInvoke (result
);
352 protected override void OnClose (TimeSpan timeout
)
354 DateTime start
= DateTime
.Now
;
355 channel
.Close (timeout
);
358 Action
<TimeSpan
> open_callback
;
360 protected override IAsyncResult
OnBeginOpen (
361 TimeSpan timeout
, AsyncCallback callback
, object state
)
363 if (open_callback
== null)
364 open_callback
= new Action
<TimeSpan
> (OnOpen
);
365 return open_callback
.BeginInvoke (timeout
, callback
, state
);
368 protected override void OnEndOpen (IAsyncResult result
)
370 if (open_callback
== null)
371 throw new InvalidOperationException ("Async open operation has not started");
372 open_callback
.EndInvoke (result
);
375 protected override void OnOpen (TimeSpan timeout
)
377 if (runtime
.InteractiveChannelInitializers
.Count
> 0 && !DidInteractiveInitialization
)
378 throw new InvalidOperationException ("The client runtime is assigned interactive channel initializers, and in such case DisplayInitializationUI must be called before the channel is opened.");
379 if (channel
.State
== CommunicationState
.Created
)
380 channel
.Open (timeout
);
385 IChannel OperationChannel
{
386 get { return channel; }
389 public T GetProperty
<T
> () where T
: class
391 return OperationChannel
.GetProperty
<T
> ();
394 // IExtensibleObject<IContextChannel>
396 IExtensionCollection
<IContextChannel
> extensions
;
398 public IExtensionCollection
<IContextChannel
> Extensions
{
400 if (extensions
== null)
401 extensions
= new ExtensionCollection
<IContextChannel
> (this);
406 #region Request/Output processing
408 public IAsyncResult
BeginProcess (MethodBase method
, string operationName
, object [] parameters
, AsyncCallback callback
, object asyncState
)
411 throw new InvalidOperationException ("another operation is in progress");
412 context
= OperationContext
.Current
;
413 return _processDelegate
.BeginInvoke (method
, operationName
, parameters
, callback
, asyncState
);
416 public object EndProcess (MethodBase method
, string operationName
, object [] parameters
, IAsyncResult result
)
420 throw new ArgumentNullException ("result");
421 if (parameters
== null)
422 throw new ArgumentNullException ("parameters");
423 // FIXME: the method arguments should be verified to be
424 // identical to the arguments in the corresponding begin method.
425 return _processDelegate
.EndInvoke (result
);
428 public object Process (MethodBase method
, string operationName
, object [] parameters
)
431 return DoProcess (method
, operationName
, parameters
);
432 } catch (Exception ex
) {
433 #if MOONLIGHT // just for debugging
434 Console
.Write ("Exception in async operation: ");
435 Console
.WriteLine (ex
);
441 object DoProcess (MethodBase method
, string operationName
, object [] parameters
)
443 if (AllowInitializationUI
)
444 DisplayInitializationUI ();
445 OperationDescription od
= SelectOperation (method
, operationName
, parameters
);
447 if (State
!= CommunicationState
.Opened
)
451 return Request (od
, parameters
);
453 Output (od
, parameters
);
458 OperationDescription
SelectOperation (MethodBase method
, string operationName
, object [] parameters
)
461 if (Runtime
.OperationSelector
!= null)
462 operation
= Runtime
.OperationSelector
.SelectOperation (method
, parameters
);
464 operation
= operationName
;
465 OperationDescription od
= contract
.Operations
.Find (operation
);
467 throw new Exception (String
.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation
));
471 void Output (OperationDescription od
, object [] parameters
)
473 ClientOperation op
= runtime
.Operations
[od
.Name
];
474 Send (CreateRequest (op
, parameters
), OperationTimeout
);
477 object Request (OperationDescription od
, object [] parameters
)
479 ClientOperation op
= runtime
.Operations
[od
.Name
];
480 object [] inspections
= new object [runtime
.MessageInspectors
.Count
];
481 Message req
= CreateRequest (op
, parameters
);
483 for (int i
= 0; i
< inspections
.Length
; i
++)
484 inspections
[i
] = runtime
.MessageInspectors
[i
].BeforeSendRequest (ref req
, this);
486 Message res
= Request (req
, OperationTimeout
);
488 var resb
= res
.CreateBufferedCopy (runtime
.MaxFaultSize
);
489 MessageFault fault
= MessageFault
.CreateFault (resb
.CreateMessage (), runtime
.MaxFaultSize
);
490 var conv
= OperationChannel
.GetProperty
<FaultConverter
> () ?? FaultConverter
.GetDefaultFaultConverter (res
.Version
);
492 if (!conv
.TryCreateException (resb
.CreateMessage (), fault
, out ex
)) {
493 if (fault
.HasDetail
) {
494 Type detailType
= typeof (ExceptionDetail
);
495 var freader
= fault
.GetReaderAtDetailContents ();
496 DataContractSerializer ds
= null;
498 foreach (var fci
in op
.FaultContractInfos
)
499 if (res
.Headers
.Action
== fci
.Action
|| fci
.Serializer
.IsStartObject (freader
)) {
500 detailType
= fci
.Detail
;
506 ds
= new DataContractSerializer (detailType
);
507 var detail
= ds
.ReadObject (freader
);
508 ex
= (Exception
) Activator
.CreateInstance (typeof (FaultException
<>).MakeGenericType (detailType
), new object [] {detail, fault.Reason, fault.Code, res.Headers.Action}
);
512 ex
= new FaultException (fault
);
517 for (int i
= 0; i
< inspections
.Length
; i
++)
518 runtime
.MessageInspectors
[i
].AfterReceiveReply (ref res
, inspections
[i
]);
520 if (op
.DeserializeReply
)
521 return op
.Formatter
.DeserializeReply (res
, parameters
);
526 #region Message-based Request() and Send()
527 // They are internal for ClientBase<T>.ChannelBase use.
528 internal Message
Request (Message msg
, TimeSpan timeout
)
530 if (RequestChannel
!= null)
531 return RequestChannel
.Request (msg
, timeout
);
533 DateTime startTime
= DateTime
.Now
;
534 OutputChannel
.Send (msg
, timeout
);
535 return ((IDuplexChannel
) OutputChannel
).Receive (timeout
- (DateTime
.Now
- startTime
));
539 internal IAsyncResult
BeginRequest (Message msg
, TimeSpan timeout
, AsyncCallback callback
, object state
)
541 return requestDelegate
.BeginInvoke (msg
, timeout
, callback
, state
);
544 internal Message
EndRequest (IAsyncResult result
)
546 return requestDelegate
.EndInvoke (result
);
549 internal void Send (Message msg
, TimeSpan timeout
)
551 OutputChannel
.Send (msg
, timeout
);
554 internal IAsyncResult
BeginSend (Message msg
, TimeSpan timeout
, AsyncCallback callback
, object state
)
556 return sendDelegate
.BeginInvoke (msg
, timeout
, callback
, state
);
559 internal void EndSend (IAsyncResult result
)
561 sendDelegate
.EndInvoke (result
);
565 Message
CreateRequest (ClientOperation op
, object [] parameters
)
567 MessageVersion version
= message_version
;
569 version
= MessageVersion
.Default
;
572 if (op
.SerializeRequest
)
573 msg
= op
.Formatter
.SerializeRequest (
574 version
, parameters
);
576 if (parameters
.Length
!= 1)
577 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
));
578 if (!(parameters
[0] is Message
))
579 throw new ArgumentException (String
.Format ("Argument should be only a Message, but has {0}", parameters
[0] != null ? parameters
[0].GetType ().FullName
: "null"));
580 msg
= (Message
) parameters
[0];
583 context
= context
?? OperationContext
.Current
;
584 if (context
!= null) {
585 // CopyHeadersFrom does not work here (brings duplicates -> error)
586 foreach (var mh
in context
.OutgoingMessageHeaders
) {
587 int x
= msg
.Headers
.FindHeader (mh
.Name
, mh
.Namespace
, mh
.Actor
);
589 msg
.Headers
.RemoveAt (x
);
590 msg
.Headers
.Add ((MessageHeader
) mh
);
592 msg
.Properties
.CopyProperties (context
.OutgoingMessageProperties
);
595 if (OutputSession
!= null)
596 msg
.Headers
.MessageId
= new UniqueId (OutputSession
.Id
);
597 msg
.Properties
.AllowOutputBatching
= AllowOutputBatching
;
599 if (msg
.Version
.Addressing
.Equals (AddressingVersion
.WSAddressing10
)) {
600 if (msg
.Headers
.MessageId
== null)
601 msg
.Headers
.MessageId
= new UniqueId ();
602 if (msg
.Headers
.ReplyTo
== null)
603 msg
.Headers
.ReplyTo
= new EndpointAddress (Constants
.WsaAnonymousUri
);
604 if (msg
.Headers
.To
== null)
605 msg
.Headers
.To
= RemoteAddress
.Uri
;