2010-06-03 Jb Evain <jbevain@novell.com>
[mcs.git] / class / System.ServiceModel / System.ServiceModel / ClientRuntimeChannel.cs
blob53b021a0eb5d76fef6b7502afa44ca342d89ded6
1 //
2 // ClientRuntimeChannel.cs
3 //
4 // Author:
5 // Atsushi Enomoto <atsushi@ximian.com>
6 //
7 // Copyright (C) 2006 Novell, Inc. http://www.novell.com
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
28 using System;
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;
36 using System.Xml;
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;
49 IChannel channel;
50 IChannelFactory factory;
51 OperationContext context;
53 #region delegates
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);
65 #endregion
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;
77 runtime.Via = via;
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);
86 // default values
87 AllowInitializationUI = true;
88 OperationTimeout = TimeSpan.FromMinutes (1);
90 if (contextChannel != null)
91 channel = contextChannel;
92 else {
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; }
129 public Uri Via {
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)
144 if (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 ();
153 return true;
156 public override bool WaitOne (int millisecondsTimeout)
158 return WaitHandle.WaitAll (ResultWaitHandles, millisecondsTimeout);
161 WaitHandle [] ResultWaitHandles {
162 get {
163 var arr = new WaitHandle [results.Length];
164 for (int i = 0; i < arr.Length; i++)
165 arr [i] = results [i].AsyncWaitHandle;
166 return arr;
170 #if !MOONLIGHT
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);
180 #endif
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 {
197 get { return null; }
200 WaitHandle wait_handle;
202 public WaitHandle AsyncWaitHandle {
203 get {
204 if (wait_handle == null)
205 wait_handle = new DelegatingWaitHandle (results);
206 return wait_handle;
210 public bool CompletedSynchronously {
211 get {
212 foreach (var r in results)
213 if (!r.CompletedSynchronously)
214 return false;
215 return true;
218 public bool IsCompleted {
219 get {
220 foreach (var r in results)
221 if (!r.IsCompleted)
222 return false;
223 return true;
228 public IAsyncResult BeginDisplayInitializationUI (
229 AsyncCallback callback, object state)
231 OnInitializationUI ();
232 IAsyncResult [] arr = new IAsyncResult [runtime.InteractiveChannelInitializers.Count];
233 int i = 0;
234 foreach (var init in runtime.InteractiveChannelInitializers)
235 arr [i++] = init.BeginDisplayInitializationUI (this, callback, state);
236 return new DisplayUIAsyncResult (arr);
239 public void EndDisplayInitializationUI (
240 IAsyncResult result)
242 DisplayUIAsyncResult r = (DisplayUIAsyncResult) result;
243 int i = 0;
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 ()
267 Close ();
270 public event EventHandler<UnknownMessageReceivedEventArgs> UnknownMessageReceived;
272 #endregion
274 #region IContextChannel
276 [MonoTODO]
277 public bool AllowOutputBatching { get; set; }
279 public IInputSession InputSession {
280 get {
281 ISessionChannel<IInputSession> ch = RequestChannel as ISessionChannel<IInputSession>;
282 ch = ch ?? OutputChannel as ISessionChannel<IInputSession>;
283 if (ch != null)
284 return ch.Session;
285 var dch = OutputChannel as ISessionChannel<IDuplexSession>;
286 return dch != null ? dch.Session : null;
290 public EndpointAddress LocalAddress {
291 get {
292 var dc = OperationChannel as IDuplexChannel;
293 return dc != null ? dc.LocalAddress : null;
297 [MonoTODO]
298 public TimeSpan OperationTimeout { get; set; }
300 public IOutputSession OutputSession {
301 get {
302 ISessionChannel<IOutputSession> ch = RequestChannel as ISessionChannel<IOutputSession>;
303 ch = ch ?? OutputChannel as ISessionChannel<IOutputSession>;
304 if (ch != null)
305 return ch.Session;
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; }
319 #endregion
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 ()
332 channel.Abort ();
333 if (factory != null) // ... is it valid?
334 factory.Abort ();
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);
383 // IChannel
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 {
399 get {
400 if (extensions == null)
401 extensions = new ExtensionCollection<IContextChannel> (this);
402 return extensions;
406 #region Request/Output processing
408 public IAsyncResult BeginProcess (MethodBase method, string operationName, object [] parameters, AsyncCallback callback, object asyncState)
410 if (context != null)
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)
418 context = null;
419 if (result == null)
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)
430 try {
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);
436 #endif
437 throw;
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)
448 Open ();
450 if (!od.IsOneWay)
451 return Request (od, parameters);
452 else {
453 Output (od, parameters);
454 return null;
458 OperationDescription SelectOperation (MethodBase method, string operationName, object [] parameters)
460 string operation;
461 if (Runtime.OperationSelector != null)
462 operation = Runtime.OperationSelector.SelectOperation (method, parameters);
463 else
464 operation = operationName;
465 OperationDescription od = contract.Operations.Find (operation);
466 if (od == null)
467 throw new Exception (String.Format ("OperationDescription for operation '{0}' was not found in its internally-generated contract.", operation));
468 return od;
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);
487 if (res.IsFault) {
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);
491 Exception ex;
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;
497 #if !NET_2_1
498 foreach (var fci in op.FaultContractInfos)
499 if (res.Headers.Action == fci.Action || fci.Serializer.IsStartObject (freader)) {
500 detailType = fci.Detail;
501 ds = fci.Serializer;
502 break;
504 #endif
505 if (ds == null)
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});
511 if (ex == null)
512 ex = new FaultException (fault);
514 throw ex;
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);
522 else
523 return res;
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);
532 else {
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);
563 #endregion
565 Message CreateRequest (ClientOperation op, object [] parameters)
567 MessageVersion version = message_version;
568 if (version == null)
569 version = MessageVersion.Default;
571 Message msg;
572 if (op.SerializeRequest)
573 msg = op.Formatter.SerializeRequest (
574 version, parameters);
575 else {
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);
588 if (x >= 0)
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;
608 return msg;
611 #endregion