5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2005-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.Collections.ObjectModel;
31 using System.ServiceModel.Channels;
32 using System.ServiceModel.Configuration;
33 using System.ServiceModel.Description;
34 using System.ServiceModel.Dispatcher;
35 using System.ServiceModel.Security;
36 using System.Reflection;
38 namespace System.ServiceModel
40 public abstract partial class ServiceHostBase
41 : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
43 ServiceCredentials credentials;
44 ServiceDescription description;
45 UriSchemeKeyedCollection base_addresses;
46 TimeSpan open_timeout, close_timeout, instance_idle_timeout;
47 ServiceThrottle throttle;
48 List<InstanceContext> contexts;
49 ReadOnlyCollection<InstanceContext> exposed_contexts;
50 ChannelDispatcherCollection channel_dispatchers;
51 IDictionary<string,ContractDescription> contracts;
52 int flow_limit = int.MaxValue;
53 IExtensionCollection<ServiceHostBase> extensions;
55 protected ServiceHostBase ()
57 open_timeout = DefaultOpenTimeout;
58 close_timeout = DefaultCloseTimeout;
60 credentials = new ServiceCredentials ();
61 throttle = new ServiceThrottle ();
62 contexts = new List<InstanceContext> ();
63 exposed_contexts = new ReadOnlyCollection<InstanceContext> (contexts);
64 channel_dispatchers = new ChannelDispatcherCollection (this);
67 public event EventHandler<UnknownMessageReceivedEventArgs>
68 UnknownMessageReceived;
70 internal void OnUnknownMessageReceived (Message message)
72 if (UnknownMessageReceived != null)
73 UnknownMessageReceived (this, new UnknownMessageReceivedEventArgs (message));
75 // FIXME: better be logged
76 throw new EndpointNotFoundException (String.Format ("The request message has the target '{0}' with action '{1}' which is not reachable in this service contract", message.Headers.To, message.Headers.Action));
79 public ReadOnlyCollection<Uri> BaseAddresses {
80 get { return new ReadOnlyCollection<Uri> (base_addresses.InternalItems); }
83 internal Uri CreateUri (string sheme, Uri relatieUri) {
84 Uri baseUri = base_addresses.Contains (sheme) ? base_addresses [sheme] : null;
86 if (relatieUri == null)
88 if (relatieUri.IsAbsoluteUri)
92 return new Uri (baseUri, relatieUri);
95 public ChannelDispatcherCollection ChannelDispatchers {
96 get { return channel_dispatchers; }
99 public ServiceAuthorizationBehavior Authorization {
105 public ServiceCredentials Credentials {
106 get { return credentials; }
109 public ServiceDescription Description {
110 get { return description; }
113 protected IDictionary<string,ContractDescription> ImplementedContracts {
114 get { return contracts; }
118 public IExtensionCollection<ServiceHostBase> Extensions {
120 if (extensions == null)
121 extensions = new ExtensionCollection<ServiceHostBase> (this);
126 protected internal override TimeSpan DefaultCloseTimeout {
127 get { return DefaultCommunicationTimeouts.Instance.CloseTimeout; }
130 protected internal override TimeSpan DefaultOpenTimeout {
131 get { return DefaultCommunicationTimeouts.Instance.OpenTimeout; }
134 public TimeSpan CloseTimeout {
135 get { return close_timeout; }
136 set { close_timeout = value; }
139 public TimeSpan OpenTimeout {
140 get { return open_timeout; }
141 set { open_timeout = value; }
144 public int ManualFlowControlLimit {
145 get { return flow_limit; }
146 set { flow_limit = value; }
149 public ServiceEndpoint AddServiceEndpoint (
150 string implementedContract, Binding binding, string address)
152 return AddServiceEndpoint (implementedContract,
154 new Uri (address, UriKind.RelativeOrAbsolute));
157 public ServiceEndpoint AddServiceEndpoint (
158 string implementedContract, Binding binding,
159 string address, Uri listenUri)
161 Uri uri = new Uri (address, UriKind.RelativeOrAbsolute);
162 return AddServiceEndpoint (
163 implementedContract, binding, uri, uri);
166 public ServiceEndpoint AddServiceEndpoint (
167 string implementedContract, Binding binding,
170 return AddServiceEndpoint (implementedContract, binding, address, address);
173 public ServiceEndpoint AddServiceEndpoint (
174 string implementedContract, Binding binding,
175 Uri address, Uri listenUri)
177 EndpointAddress ea = BuildEndpointAddress (address, binding);
178 ContractDescription cd = GetContract (implementedContract);
180 throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
181 return AddServiceEndpointCore (cd, binding, ea, listenUri);
184 Type PopulateType (string typeName)
186 Type type = Type.GetType (typeName);
189 foreach (ContractDescription cd in ImplementedContracts.Values) {
190 type = cd.ContractType.Assembly.GetType (typeName);
197 ContractDescription GetContract (string typeName)
199 //FIXME: hack hack hack
200 ImplementedContracts ["IHttpGetHelpPageAndMetadataContract"] =
201 ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract));
203 // FIXME: As long as I tried, *only* IMetadataExchange
204 // is the exception case that does not require full
205 // type name. Hence I treat it as a special case.
206 if (typeName == ServiceMetadataBehavior.MexContractName) {
207 if (!Description.Behaviors.Contains (typeof (ServiceMetadataBehavior)) && Array.IndexOf (Description.ServiceType.GetInterfaces (), typeof (IMetadataExchange)) < 0)
208 throw new InvalidOperationException (
209 "Add ServiceMetadataBehavior to the ServiceHost to add a endpoint for IMetadataExchange contract.");
211 ImplementedContracts [ServiceMetadataBehavior.MexContractName] =
212 ContractDescription.GetContract (typeof (IMetadataExchange));
214 foreach (ContractDescription cd in ImplementedContracts.Values)
215 if (cd.ContractType == typeof (IMetadataExchange))
220 Type type = PopulateType (typeName);
224 foreach (ContractDescription cd in ImplementedContracts.Values) {
225 // FIXME: This check is a negative side effect
226 // of the above hack.
227 if (cd.ContractType == typeof (IMetadataExchange))
230 if (cd.ContractType == type ||
231 cd.ContractType.IsSubclassOf (type) ||
232 type.IsInterface && cd.ContractType.GetInterface (type.FullName) == type)
238 internal EndpointAddress BuildEndpointAddress (Uri address, Binding binding)
240 if (!address.IsAbsoluteUri) {
241 // Find a Base address with matching scheme,
242 // and build new absolute address
243 if (!base_addresses.Contains (binding.Scheme))
244 throw new InvalidOperationException (String.Format ("Could not find base address that matches Scheme {0} for endpoint {1}", binding.Scheme, binding.Name));
246 Uri baseaddr = base_addresses [binding.Scheme];
248 if (!baseaddr.AbsoluteUri.EndsWith ("/"))
249 baseaddr = new Uri (baseaddr.AbsoluteUri + "/");
250 address = new Uri (baseaddr, address);
252 return new EndpointAddress (address);
255 internal ServiceEndpoint AddServiceEndpointCore (
256 ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
258 foreach (ServiceEndpoint e in Description.Endpoints)
259 if (e.Contract == cd)
261 ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
262 se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
263 Description.Endpoints.Add (se);
268 protected virtual void ApplyConfiguration ()
270 if (Description == null)
271 throw new InvalidOperationException ("ApplyConfiguration requires that the Description property be initialized. Either provide a valid ServiceDescription in the CreateDescription method or override the ApplyConfiguration method to provide an alternative implementation");
273 ServiceElement service = GetServiceElement ();
275 //TODO: Should we call here LoadServiceElement ?
276 if (service != null) {
279 HostElement host = service.Host;
280 foreach (BaseAddressElement baseAddress in host.BaseAddresses) {
281 this.base_addresses.Add (new Uri (baseAddress.BaseAddress));
285 foreach (ServiceEndpointElement endpoint in service.Endpoints) {
286 // FIXME: consider BindingName as well
287 ServiceEndpoint se = AddServiceEndpoint (
289 ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
290 endpoint.Address.ToString ());
293 // TODO: use EvaluationContext of ServiceElement.
294 ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors.Find (service.BehaviorConfiguration);
295 if (behavior != null) {
296 for (int i = 0; i < behavior.Count; i++) {
297 BehaviorExtensionElement bxel = behavior [i];
298 IServiceBehavior b = (IServiceBehavior) behavior [i].CreateBehavior ();
300 Description.Behaviors.Add (b);
304 // TODO: consider commonBehaviors here
306 // ensure ServiceAuthorizationBehavior
307 Authorization = Description.Behaviors.Find<ServiceAuthorizationBehavior> ();
308 if (Authorization == null) {
309 Authorization = new ServiceAuthorizationBehavior ();
310 Description.Behaviors.Add (Authorization);
313 // ensure ServiceDebugBehavior
314 ServiceDebugBehavior debugBehavior = Description.Behaviors.Find<ServiceDebugBehavior> ();
315 if (debugBehavior == null) {
316 debugBehavior = new ServiceDebugBehavior ();
317 Description.Behaviors.Add (debugBehavior);
321 private ServiceElement GetServiceElement() {
322 Type serviceType = Description.ServiceType;
323 if (serviceType == null)
326 return ConfigUtil.ServicesSection.Services [serviceType.FullName];
329 internal ContractDescription GetContract (string name, string ns)
331 foreach (ContractDescription d in ImplementedContracts.Values)
332 if (d.Name == name && d.Namespace == ns)
337 protected abstract ServiceDescription CreateDescription (
338 out IDictionary<string,ContractDescription> implementedContracts);
340 protected void InitializeDescription (UriSchemeKeyedCollection baseAddresses)
342 this.base_addresses = baseAddresses;
343 IDictionary<string,ContractDescription> retContracts;
344 description = CreateDescription (out retContracts);
345 contracts = retContracts;
347 ApplyConfiguration ();
350 protected virtual void InitializeRuntime ()
352 //First validate the description, which should call all behaviors
354 ValidateDescription ();
356 //Build all ChannelDispatchers, one dispatcher per user configured EndPoint.
357 //We must keep thet ServiceEndpoints as a seperate collection, since the user
358 //can change the collection in the description during the behaviors events.
359 Dictionary<ServiceEndpoint, ChannelDispatcher> endPointToDispatcher = new Dictionary<ServiceEndpoint,ChannelDispatcher>();
360 ServiceEndpoint[] endPoints = new ServiceEndpoint[Description.Endpoints.Count];
361 Description.Endpoints.CopyTo (endPoints, 0);
362 foreach (ServiceEndpoint se in endPoints) {
363 ChannelDispatcher channel = BuildChannelDispatcher (se);
364 ChannelDispatchers.Add (channel);
365 endPointToDispatcher[se] = channel;
368 //After the ChannelDispatchers are created, and attached to the service host
369 //Apply dispatching behaviors.
370 foreach (IServiceBehavior b in Description.Behaviors)
371 b.ApplyDispatchBehavior (Description, this);
373 foreach(KeyValuePair<ServiceEndpoint, ChannelDispatcher> val in endPointToDispatcher)
374 foreach (var ed in val.Value.Endpoints)
375 ApplyDispatchBehavior (ed, val.Key);
378 private void ValidateDescription ()
380 foreach (IServiceBehavior b in Description.Behaviors)
381 b.Validate (Description, this);
382 foreach (ServiceEndpoint endPoint in Description.Endpoints)
383 endPoint.Validate ();
386 private void ApplyDispatchBehavior (EndpointDispatcher ed, ServiceEndpoint endPoint)
388 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
389 b.ApplyDispatchBehavior (endPoint.Contract, endPoint, ed.DispatchRuntime);
390 foreach (IEndpointBehavior b in endPoint.Behaviors)
391 b.ApplyDispatchBehavior (endPoint, ed);
392 foreach (OperationDescription operation in endPoint.Contract.Operations) {
393 foreach (IOperationBehavior b in operation.Behaviors)
394 b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
399 internal ChannelDispatcher BuildChannelDispatcher (ServiceEndpoint se)
401 var commonParams = new BindingParameterCollection ();
402 foreach (IServiceBehavior b in Description.Behaviors)
403 b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
405 return new DispatcherBuilder ().BuildChannelDispatcher (Description.ServiceType, se, commonParams);
409 partial class DispatcherBuilder
411 internal ChannelDispatcher BuildChannelDispatcher (Type serviceType, ServiceEndpoint se, BindingParameterCollection commonParams)
413 //Let all behaviors add their binding parameters
414 AddBindingParameters (commonParams, se);
415 //User the binding parameters to build the channel listener and Dispatcher
416 IChannelListener lf = BuildListener (se, commonParams);
417 ChannelDispatcher cd = new ChannelDispatcher (
418 lf, se.Binding.Name);
419 cd.InitializeServiceEndpoint (serviceType, se);
423 private void AddBindingParameters (BindingParameterCollection commonParams, ServiceEndpoint endPoint) {
425 commonParams.Add (ChannelProtectionRequirements.CreateFromContract (endPoint.Contract));
427 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
428 b.AddBindingParameters (endPoint.Contract, endPoint, commonParams);
429 foreach (IEndpointBehavior b in endPoint.Behaviors)
430 b.AddBindingParameters (endPoint, commonParams);
431 foreach (OperationDescription operation in endPoint.Contract.Operations) {
432 foreach (IOperationBehavior b in operation.Behaviors)
433 b.AddBindingParameters (operation, commonParams);
438 public abstract partial class ServiceHostBase
441 protected void LoadConfigurationSection (ServiceElement element)
443 ServicesSection services = ConfigUtil.ServicesSection;
446 void DoOpen (TimeSpan timeout)
448 foreach (var cd in ChannelDispatchers) {
450 // This is likely hack.
451 if (cd is ChannelDispatcher)
452 ((ChannelDispatcher) cd).StartLoop ();
457 partial class DispatcherBuilder
459 static IChannelListener BuildListener (ServiceEndpoint se,
460 BindingParameterCollection pl)
462 Binding b = se.Binding;
463 if (b.CanBuildChannelListener<IReplySessionChannel> (pl))
464 return b.BuildChannelListener<IReplySessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
465 if (b.CanBuildChannelListener<IReplyChannel> (pl))
466 return b.BuildChannelListener<IReplyChannel> (se.ListenUri, "", se.ListenUriMode, pl);
467 if (b.CanBuildChannelListener<IInputSessionChannel> (pl))
468 return b.BuildChannelListener<IInputSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
469 if (b.CanBuildChannelListener<IInputChannel> (pl))
470 return b.BuildChannelListener<IInputChannel> (se.ListenUri, "", se.ListenUriMode, pl);
472 if (b.CanBuildChannelListener<IDuplexChannel> (pl))
473 return b.BuildChannelListener<IDuplexChannel> (se.ListenUri, "", se.ListenUriMode, pl);
474 if (b.CanBuildChannelListener<IDuplexSessionChannel> (pl))
475 return b.BuildChannelListener<IDuplexSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
476 throw new InvalidOperationException ("None of the listener channel types is supported");
480 public abstract partial class ServiceHostBase
483 protected override sealed void OnAbort ()
487 Action<TimeSpan> close_delegate;
488 Action<TimeSpan> open_delegate;
490 protected override sealed IAsyncResult OnBeginClose (
491 TimeSpan timeout, AsyncCallback callback, object state)
493 if (close_delegate != null)
494 close_delegate = new Action<TimeSpan> (OnClose);
495 return close_delegate.BeginInvoke (timeout, callback, state);
498 protected override sealed IAsyncResult OnBeginOpen (
499 TimeSpan timeout, AsyncCallback callback, object state)
501 if (open_delegate == null)
502 open_delegate = new Action<TimeSpan> (OnOpen);
503 return open_delegate.BeginInvoke (timeout, callback, state);
506 protected override void OnClose (TimeSpan timeout)
508 DateTime start = DateTime.Now;
509 ReleasePerformanceCounters ();
510 List<ChannelDispatcherBase> l = new List<ChannelDispatcherBase> (ChannelDispatchers);
511 foreach (ChannelDispatcherBase e in l) {
513 TimeSpan ts = timeout - (DateTime.Now - start);
514 if (ts < TimeSpan.Zero)
518 } catch (Exception ex) {
519 Console.WriteLine ("ServiceHostBase failed to close the channel dispatcher:");
520 Console.WriteLine (ex);
525 protected override sealed void OnOpen (TimeSpan timeout)
527 InitializeRuntime ();
531 protected override void OnEndClose (IAsyncResult result)
533 if (close_delegate == null)
534 throw new InvalidOperationException ("Async close operation has not started");
535 close_delegate.EndInvoke (result);
538 protected override sealed void OnEndOpen (IAsyncResult result)
540 if (open_delegate == null)
541 throw new InvalidOperationException ("Aync open operation has not started");
542 open_delegate.EndInvoke (result);
545 protected override void OnOpened ()
550 protected void ReleasePerformanceCounters ()
554 void IDisposable.Dispose ()
560 class SyncMethodInvoker : IOperationInvoker
562 readonly MethodInfo _methodInfo;
563 public SyncMethodInvoker (MethodInfo methodInfo) {
564 _methodInfo = methodInfo;
567 #region IOperationInvoker Members
569 public bool IsSynchronous {
573 public object [] AllocateParameters () {
574 return new object [_methodInfo.GetParameters ().Length];
577 public object Invoke (object instance, object [] parameters)
579 return _methodInfo.Invoke (instance, parameters);
582 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
583 throw new NotSupportedException ();
586 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result) {
587 throw new NotSupportedException ();
593 class AsyncMethodInvoker : IOperationInvoker
595 readonly MethodInfo _beginMethodInfo, _endMethodInfo;
596 public AsyncMethodInvoker (MethodInfo beginMethodInfo, MethodInfo endMethodInfo) {
597 _beginMethodInfo = beginMethodInfo;
598 _endMethodInfo = endMethodInfo;
601 #region IOperationInvoker Members
603 public bool IsSynchronous {
604 get { return false; }
607 public object [] AllocateParameters () {
608 return new object [_beginMethodInfo.GetParameters ().Length - 2 + _endMethodInfo.GetParameters().Length-1];
611 public object Invoke (object instance, object [] parameters) {
612 throw new NotImplementedException ("Can't invoke async method synchronously");
613 //BUGBUG: need to differentiate between input and output parameters.
614 IAsyncResult asyncResult = InvokeBegin(instance, parameters, delegate(IAsyncResult ignore) { }, null);
615 asyncResult.AsyncWaitHandle.WaitOne();
616 return InvokeEnd(instance, out parameters, asyncResult);
619 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
620 if (inputs.Length + 2 != _beginMethodInfo.GetParameters ().Length)
621 throw new ArgumentException ("Wrong number of input parameters");
622 object [] fullargs = new object [_beginMethodInfo.GetParameters ().Length];
623 Array.Copy (inputs, fullargs, inputs.Length);
624 fullargs [inputs.Length] = callback;
625 fullargs [inputs.Length + 1] = state;
626 return (IAsyncResult) _beginMethodInfo.Invoke (instance, fullargs);
629 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult asyncResult) {
630 outputs = new object [_endMethodInfo.GetParameters ().Length - 1];
631 object [] fullargs = new object [_endMethodInfo.GetParameters ().Length];
632 fullargs [outputs.Length] = asyncResult;
633 object result = _endMethodInfo.Invoke (instance, fullargs);
634 Array.Copy (fullargs, outputs, outputs.Length);