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;
32 using System.ServiceModel.Channels;
33 using System.ServiceModel.Configuration;
34 using System.ServiceModel.Description;
35 using System.ServiceModel.Dispatcher;
36 using System.ServiceModel.Security;
37 using System.Reflection;
39 namespace System.ServiceModel
41 public abstract partial class ServiceHostBase
42 : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
44 // It is used for mapping a ServiceHostBase to HttpChannelListener precisely.
45 internal static ServiceHostBase CurrentServiceHostHack;
47 ServiceCredentials credentials;
48 ServiceDescription description;
49 UriSchemeKeyedCollection base_addresses;
50 TimeSpan open_timeout, close_timeout, instance_idle_timeout;
51 ServiceThrottle throttle;
52 List<InstanceContext> contexts;
53 ReadOnlyCollection<InstanceContext> exposed_contexts;
54 ChannelDispatcherCollection channel_dispatchers;
55 IDictionary<string,ContractDescription> contracts;
56 int flow_limit = int.MaxValue;
57 IExtensionCollection<ServiceHostBase> extensions;
59 protected ServiceHostBase ()
61 open_timeout = DefaultOpenTimeout;
62 close_timeout = DefaultCloseTimeout;
64 credentials = new ServiceCredentials ();
65 throttle = new ServiceThrottle ();
66 contexts = new List<InstanceContext> ();
67 exposed_contexts = new ReadOnlyCollection<InstanceContext> (contexts);
68 channel_dispatchers = new ChannelDispatcherCollection (this);
71 public event EventHandler<UnknownMessageReceivedEventArgs>
72 UnknownMessageReceived;
74 internal void OnUnknownMessageReceived (Message message)
76 if (UnknownMessageReceived != null)
77 UnknownMessageReceived (this, new UnknownMessageReceivedEventArgs (message));
79 // FIXME: better be logged
80 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));
83 public ReadOnlyCollection<Uri> BaseAddresses {
85 if (base_addresses == null)
86 base_addresses = new UriSchemeKeyedCollection ();
87 return new ReadOnlyCollection<Uri> (base_addresses.InternalItems);
91 internal Uri CreateUri (string scheme, Uri relativeUri)
93 Uri baseUri = base_addresses.Contains (scheme) ? base_addresses [scheme] : null;
95 if (relativeUri == null)
97 if (relativeUri.IsAbsoluteUri)
101 var s = relativeUri.ToString ();
104 var l = baseUri.LocalPath;
105 var r = relativeUri.ToString ();
107 if (l.Length > 0 && l [l.Length - 1] != '/' && r [0] != '/')
108 return new Uri (String.Concat (baseUri.ToString (), "/", r));
110 return new Uri (String.Concat (baseUri.ToString (), r));
113 public ChannelDispatcherCollection ChannelDispatchers {
114 get { return channel_dispatchers; }
117 public ServiceAuthorizationBehavior Authorization {
123 public ServiceCredentials Credentials {
124 get { return credentials; }
127 public ServiceDescription Description {
128 get { return description; }
131 protected IDictionary<string,ContractDescription> ImplementedContracts {
132 get { return contracts; }
136 public IExtensionCollection<ServiceHostBase> Extensions {
138 if (extensions == null)
139 extensions = new ExtensionCollection<ServiceHostBase> (this);
144 protected internal override TimeSpan DefaultCloseTimeout {
145 get { return DefaultCommunicationTimeouts.Instance.CloseTimeout; }
148 protected internal override TimeSpan DefaultOpenTimeout {
149 get { return DefaultCommunicationTimeouts.Instance.OpenTimeout; }
152 public TimeSpan CloseTimeout {
153 get { return close_timeout; }
154 set { close_timeout = value; }
157 public TimeSpan OpenTimeout {
158 get { return open_timeout; }
159 set { open_timeout = value; }
162 public int ManualFlowControlLimit {
163 get { return flow_limit; }
164 set { flow_limit = value; }
167 protected void AddBaseAddress (Uri baseAddress)
169 if (base_addresses == null)
170 throw new InvalidOperationException ("Base addresses must be added before the service description is initialized");
171 base_addresses.Add (baseAddress);
174 public ServiceEndpoint AddServiceEndpoint (
175 string implementedContract, Binding binding, string address)
177 return AddServiceEndpoint (implementedContract,
179 new Uri (address, UriKind.RelativeOrAbsolute));
182 public ServiceEndpoint AddServiceEndpoint (
183 string implementedContract, Binding binding,
184 string address, Uri listenUri)
186 Uri uri = new Uri (address, UriKind.RelativeOrAbsolute);
187 return AddServiceEndpoint (
188 implementedContract, binding, uri, listenUri);
191 public ServiceEndpoint AddServiceEndpoint (
192 string implementedContract, Binding binding,
195 return AddServiceEndpoint (implementedContract, binding, address, address);
198 public ServiceEndpoint AddServiceEndpoint (
199 string implementedContract, Binding binding,
200 Uri address, Uri listenUri)
202 EndpointAddress ea = BuildEndpointAddress (address, binding);
203 ContractDescription cd = GetContract (implementedContract, binding.Namespace == "http://schemas.microsoft.com/ws/2005/02/mex/bindings");
205 throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
206 return AddServiceEndpointCore (cd, binding, ea, listenUri);
209 Type PopulateType (string typeName)
211 Type type = Type.GetType (typeName);
214 foreach (ContractDescription cd in ImplementedContracts.Values) {
215 type = cd.ContractType.Assembly.GetType (typeName);
222 ContractDescription mex_contract, help_page_contract;
224 ContractDescription GetContract (string name, bool mexBinding)
226 // FIXME: not sure if they should really be special cases.
228 case "IHttpGetHelpPageAndMetadataContract":
229 if (help_page_contract == null)
230 help_page_contract = ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract));
231 return help_page_contract;
232 case "IMetadataExchange":
233 // this is certainly looking special (or we may
234 // be missing something around ServiceMetadataExtension).
235 // It seems .NET WCF has some "infrastructure"
236 // endpoints. .NET ServiceHost fails to Open()
237 // if it was added only IMetadataExchange
238 // endpoint (and you'll see the word
239 // "infrastructure" in the exception message).
240 if (mexBinding && Description.Behaviors.Find<ServiceMetadataBehavior> () == null)
242 if (mex_contract == null)
243 mex_contract = ContractDescription.GetContract (typeof (IMetadataExchange));
247 Type type = PopulateType (name);
251 foreach (ContractDescription cd in ImplementedContracts.Values) {
252 // This check is a negative side effect of the above match-by-name design.
253 if (cd.ContractType == typeof (IMetadataExchange))
256 if (cd.ContractType == type ||
257 cd.ContractType.IsSubclassOf (type) ||
258 type.IsInterface && cd.ContractType.GetInterface (type.FullName) == type)
264 internal EndpointAddress BuildEndpointAddress (Uri address, Binding binding)
266 if (!address.IsAbsoluteUri) {
267 // Find a Base address with matching scheme,
268 // and build new absolute address
269 if (!base_addresses.Contains (binding.Scheme))
270 throw new InvalidOperationException (String.Format ("Could not find base address that matches Scheme {0} for endpoint {1}", binding.Scheme, binding.Name));
272 Uri baseaddr = base_addresses [binding.Scheme];
274 if (!baseaddr.AbsoluteUri.EndsWith ("/") && address.OriginalString.Length > 0) // with empty URI it should not add '/' to possible file name of the absolute URI
275 baseaddr = new Uri (baseaddr.AbsoluteUri + "/");
276 address = new Uri (baseaddr, address);
278 return new EndpointAddress (address);
281 internal ServiceEndpoint AddServiceEndpointCore (
282 ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
284 foreach (ServiceEndpoint e in Description.Endpoints)
285 if (e.Contract == cd && e.Binding == binding && e.Address == address && e.ListenUri.Equals (listenUri))
287 ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
288 se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
289 Description.Endpoints.Add (se);
293 protected virtual void ApplyConfiguration ()
295 if (Description == null)
296 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");
298 ServiceElement service = GetServiceElement ();
300 //TODO: Should we call here LoadServiceElement ?
301 if (service != null) {
304 HostElement host = service.Host;
305 foreach (BaseAddressElement baseAddress in host.BaseAddresses) {
306 AddBaseAddress (new Uri (baseAddress.BaseAddress));
310 // TODO: use EvaluationContext of ServiceElement.
311 ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors [service.BehaviorConfiguration];
312 if (behavior != null) {
313 foreach (var bxe in behavior) {
314 IServiceBehavior b = (IServiceBehavior) bxe.CreateBehavior ();
315 Description.Behaviors.Add (b);
320 foreach (ServiceEndpointElement endpoint in service.Endpoints) {
321 ServiceEndpoint se = AddServiceEndpoint (
323 ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
324 endpoint.Address.ToString ());
325 // endpoint behaviors
326 EndpointBehaviorElement epbehavior = ConfigUtil.BehaviorsSection.EndpointBehaviors [endpoint.BehaviorConfiguration];
327 if (epbehavior != null)
328 foreach (var bxe in epbehavior) {
329 IEndpointBehavior b = (IEndpointBehavior) bxe.CreateBehavior ();
330 se.Behaviors.Add (b);
334 // TODO: consider commonBehaviors here
336 // ensure ServiceAuthorizationBehavior
337 Authorization = Description.Behaviors.Find<ServiceAuthorizationBehavior> ();
338 if (Authorization == null) {
339 Authorization = new ServiceAuthorizationBehavior ();
340 Description.Behaviors.Add (Authorization);
343 // ensure ServiceDebugBehavior
344 ServiceDebugBehavior debugBehavior = Description.Behaviors.Find<ServiceDebugBehavior> ();
345 if (debugBehavior == null) {
346 debugBehavior = new ServiceDebugBehavior ();
347 Description.Behaviors.Add (debugBehavior);
351 private ServiceElement GetServiceElement() {
352 Type serviceType = Description.ServiceType;
353 if (serviceType == null)
356 return ConfigUtil.ServicesSection.Services [serviceType.FullName];
359 protected abstract ServiceDescription CreateDescription (
360 out IDictionary<string,ContractDescription> implementedContracts);
362 protected void InitializeDescription (UriSchemeKeyedCollection baseAddresses)
364 this.base_addresses = baseAddresses;
365 IDictionary<string,ContractDescription> retContracts;
366 description = CreateDescription (out retContracts);
367 contracts = retContracts;
369 ApplyConfiguration ();
372 protected virtual void InitializeRuntime ()
374 //First validate the description, which should call all behaviors
376 ValidateDescription ();
378 //Build all ChannelDispatchers, one dispatcher per user configured EndPoint.
379 //We must keep thet ServiceEndpoints as a seperate collection, since the user
380 //can change the collection in the description during the behaviors events.
381 ServiceEndpoint[] endPoints = new ServiceEndpoint[Description.Endpoints.Count];
382 Description.Endpoints.CopyTo (endPoints, 0);
383 var builder = new DispatcherBuilder (this);
384 foreach (ServiceEndpoint se in endPoints) {
386 var commonParams = new BindingParameterCollection ();
387 foreach (IServiceBehavior b in Description.Behaviors)
388 b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
390 var channel = builder.BuildChannelDispatcher (Description.ServiceType, se, commonParams);
391 if (!ChannelDispatchers.Contains (channel))
392 ChannelDispatchers.Add (channel);
395 //After the ChannelDispatchers are created, and attached to the service host
396 //Apply dispatching behaviors.
397 foreach (IServiceBehavior b in Description.Behaviors)
398 b.ApplyDispatchBehavior (Description, this);
400 builder.ApplyDispatchBehaviors ();
403 private void ValidateDescription ()
405 foreach (IServiceBehavior b in Description.Behaviors)
406 b.Validate (Description, this);
407 foreach (ServiceEndpoint endPoint in Description.Endpoints)
408 endPoint.Validate ();
410 if (Description.Endpoints.FirstOrDefault (e => e.Contract != mex_contract) == null)
411 throw new InvalidOperationException ("The ServiceHost must have at least one application endpoint (that does not include metadata exchange contract) defined by either configuration, behaviors or call to AddServiceEndpoint methods.");
415 protected void LoadConfigurationSection (ServiceElement element)
417 ServicesSection services = ConfigUtil.ServicesSection;
421 protected override sealed void OnAbort ()
425 Action<TimeSpan> close_delegate;
426 Action<TimeSpan> open_delegate;
428 protected override sealed IAsyncResult OnBeginClose (
429 TimeSpan timeout, AsyncCallback callback, object state)
431 if (close_delegate != null)
432 close_delegate = new Action<TimeSpan> (OnClose);
433 return close_delegate.BeginInvoke (timeout, callback, state);
436 protected override sealed IAsyncResult OnBeginOpen (
437 TimeSpan timeout, AsyncCallback callback, object state)
439 if (open_delegate == null)
440 open_delegate = new Action<TimeSpan> (OnOpen);
441 return open_delegate.BeginInvoke (timeout, callback, state);
444 protected override void OnClose (TimeSpan timeout)
446 DateTime start = DateTime.Now;
447 ReleasePerformanceCounters ();
448 List<ChannelDispatcherBase> l = new List<ChannelDispatcherBase> (ChannelDispatchers);
449 foreach (ChannelDispatcherBase e in l) {
451 TimeSpan ts = timeout - (DateTime.Now - start);
452 if (ts < TimeSpan.Zero)
456 } catch (Exception ex) {
457 Console.WriteLine ("ServiceHostBase failed to close the channel dispatcher:");
458 Console.WriteLine (ex);
463 protected override sealed void OnOpen (TimeSpan timeout)
465 DateTime start = DateTime.Now;
466 InitializeRuntime ();
467 foreach (var cd in ChannelDispatchers)
468 cd.Open (timeout - (DateTime.Now - start));
470 // FIXME: remove this hack. It should make sure that each ChannelDispatcher's loop has started, using WaitHandle.WaitAll() or something similar.
471 System.Threading.Thread.Sleep (300);
474 protected override void OnEndClose (IAsyncResult result)
476 if (close_delegate == null)
477 throw new InvalidOperationException ("Async close operation has not started");
478 close_delegate.EndInvoke (result);
481 protected override sealed void OnEndOpen (IAsyncResult result)
483 if (open_delegate == null)
484 throw new InvalidOperationException ("Aync open operation has not started");
485 open_delegate.EndInvoke (result);
488 protected override void OnOpened ()
494 protected void ReleasePerformanceCounters ()
498 void IDisposable.Dispose ()
504 class SyncMethodInvoker : IOperationInvoker
506 readonly MethodInfo _methodInfo;
507 public SyncMethodInvoker (MethodInfo methodInfo) {
508 _methodInfo = methodInfo;
511 #region IOperationInvoker Members
513 public bool IsSynchronous {
517 public object [] AllocateParameters () {
518 return new object [_methodInfo.GetParameters ().Length];
521 public object Invoke (object instance, object [] parameters)
523 return _methodInfo.Invoke (instance, parameters);
526 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
527 throw new NotSupportedException ();
530 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result) {
531 throw new NotSupportedException ();
537 class AsyncMethodInvoker : IOperationInvoker
539 readonly MethodInfo _beginMethodInfo, _endMethodInfo;
540 public AsyncMethodInvoker (MethodInfo beginMethodInfo, MethodInfo endMethodInfo) {
541 _beginMethodInfo = beginMethodInfo;
542 _endMethodInfo = endMethodInfo;
545 #region IOperationInvoker Members
547 public bool IsSynchronous {
548 get { return false; }
551 public object [] AllocateParameters () {
552 return new object [_beginMethodInfo.GetParameters ().Length - 2 + _endMethodInfo.GetParameters().Length-1];
555 public object Invoke (object instance, object [] parameters) {
556 throw new NotImplementedException ("Can't invoke async method synchronously");
557 //BUGBUG: need to differentiate between input and output parameters.
558 IAsyncResult asyncResult = InvokeBegin(instance, parameters, delegate(IAsyncResult ignore) { }, null);
559 asyncResult.AsyncWaitHandle.WaitOne();
560 return InvokeEnd(instance, out parameters, asyncResult);
563 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
564 if (inputs.Length + 2 != _beginMethodInfo.GetParameters ().Length)
565 throw new ArgumentException ("Wrong number of input parameters");
566 object [] fullargs = new object [_beginMethodInfo.GetParameters ().Length];
567 Array.Copy (inputs, fullargs, inputs.Length);
568 fullargs [inputs.Length] = callback;
569 fullargs [inputs.Length + 1] = state;
570 return (IAsyncResult) _beginMethodInfo.Invoke (instance, fullargs);
573 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult asyncResult) {
574 outputs = new object [_endMethodInfo.GetParameters ().Length - 1];
575 object [] fullargs = new object [_endMethodInfo.GetParameters ().Length];
576 fullargs [outputs.Length] = asyncResult;
577 object result = _endMethodInfo.Invoke (instance, fullargs);
578 Array.Copy (fullargs, outputs, outputs.Length);
588 /// Builds ChannelDispatchers as appropriate to service the service endpoints.
590 /// <remarks>Will re-use ChannelDispatchers when two endpoint uris are the same</remarks>
591 partial class DispatcherBuilder
593 ServiceHostBase host;
595 public DispatcherBuilder (ServiceHostBase host)
600 List<ChannelDispatcher> built_dispatchers = new List<ChannelDispatcher> ();
601 Dictionary<ServiceEndpoint, EndpointDispatcher> ep_to_dispatcher_ep = new Dictionary<ServiceEndpoint, EndpointDispatcher> ();
603 internal static Action<ChannelDispatcher> ChannelDispatcherSetter;
605 internal ChannelDispatcher BuildChannelDispatcher (Type serviceType, ServiceEndpoint se, BindingParameterCollection commonParams)
607 //Let all behaviors add their binding parameters
608 AddBindingParameters (commonParams, se);
610 // See if there's an existing channel that matches this endpoint
611 ChannelDispatcher cd = FindExistingDispatcher (se);
612 EndpointDispatcher ep;
614 ep = cd.InitializeServiceEndpoint (serviceType, se);
616 // Use the binding parameters to build the channel listener and Dispatcher.
617 lock (HttpTransportBindingElement.ListenerBuildLock) {
618 ServiceHostBase.CurrentServiceHostHack = host;
619 IChannelListener lf = BuildListener (se, commonParams);
620 cd = new ChannelDispatcher (lf, se.Binding.Name);
621 if (ChannelDispatcherSetter != null) {
622 ChannelDispatcherSetter (cd);
623 ChannelDispatcherSetter = null;
625 ServiceHostBase.CurrentServiceHostHack = null;
627 ep = cd.InitializeServiceEndpoint (serviceType, se);
628 built_dispatchers.Add (cd);
630 ep_to_dispatcher_ep[se] = ep;
634 ChannelDispatcher FindExistingDispatcher (ServiceEndpoint se)
636 return built_dispatchers.FirstOrDefault ((ChannelDispatcher cd) => (cd.Listener.Uri.Equals (se.ListenUri)) && cd.MessageVersion.Equals (se.Binding.MessageVersion));
639 internal void ApplyDispatchBehaviors ()
641 foreach (KeyValuePair<ServiceEndpoint, EndpointDispatcher> val in ep_to_dispatcher_ep)
642 ApplyDispatchBehavior (val.Value, val.Key);
645 private void ApplyDispatchBehavior (EndpointDispatcher ed, ServiceEndpoint endPoint)
647 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
648 b.ApplyDispatchBehavior (endPoint.Contract, endPoint, ed.DispatchRuntime);
649 foreach (IEndpointBehavior b in endPoint.Behaviors)
650 b.ApplyDispatchBehavior (endPoint, ed);
651 foreach (OperationDescription operation in endPoint.Contract.Operations) {
652 foreach (IOperationBehavior b in operation.Behaviors)
653 b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
658 private void AddBindingParameters (BindingParameterCollection commonParams, ServiceEndpoint endPoint) {
660 commonParams.Add (ChannelProtectionRequirements.CreateFromContract (endPoint.Contract));
662 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
663 b.AddBindingParameters (endPoint.Contract, endPoint, commonParams);
664 foreach (IEndpointBehavior b in endPoint.Behaviors)
665 b.AddBindingParameters (endPoint, commonParams);
666 foreach (OperationDescription operation in endPoint.Contract.Operations) {
667 foreach (IOperationBehavior b in operation.Behaviors)
668 b.AddBindingParameters (operation, commonParams);
672 static IChannelListener BuildListener (ServiceEndpoint se,
673 BindingParameterCollection pl)
675 Binding b = se.Binding;
676 if (b.CanBuildChannelListener<IReplySessionChannel> (pl))
677 return b.BuildChannelListener<IReplySessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
678 if (b.CanBuildChannelListener<IReplyChannel> (pl))
679 return b.BuildChannelListener<IReplyChannel> (se.ListenUri, "", se.ListenUriMode, pl);
680 if (b.CanBuildChannelListener<IInputSessionChannel> (pl))
681 return b.BuildChannelListener<IInputSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
682 if (b.CanBuildChannelListener<IInputChannel> (pl))
683 return b.BuildChannelListener<IInputChannel> (se.ListenUri, "", se.ListenUriMode, pl);
685 if (b.CanBuildChannelListener<IDuplexChannel> (pl))
686 return b.BuildChannelListener<IDuplexChannel> (se.ListenUri, "", se.ListenUriMode, pl);
687 if (b.CanBuildChannelListener<IDuplexSessionChannel> (pl))
688 return b.BuildChannelListener<IDuplexSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
689 throw new InvalidOperationException ("None of the listener channel types is supported");