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 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 public ReadOnlyCollection<Uri> BaseAddresses {
71 get { return new ReadOnlyCollection<Uri> (base_addresses.InternalItems); }
74 internal Uri CreateUri (string sheme, Uri relatieUri) {
75 Uri baseUri = base_addresses.Contains (sheme) ? base_addresses [sheme] : null;
77 if (relatieUri == null)
79 if (relatieUri.IsAbsoluteUri)
83 return new Uri (baseUri, relatieUri);
87 public ChannelDispatcherCollection ChannelDispatchers {
88 get { return channel_dispatchers; }
91 public ServiceAuthorizationBehavior Authorization {
97 public ServiceCredentials Credentials {
98 get { return credentials; }
101 public ServiceDescription Description {
102 get { return description; }
105 protected IDictionary<string,ContractDescription> ImplementedContracts {
106 get { return contracts; }
110 public IExtensionCollection<ServiceHostBase> Extensions {
112 if (extensions == null)
113 extensions = new ExtensionCollection<ServiceHostBase> (this);
118 protected internal override TimeSpan DefaultCloseTimeout {
119 get { return DefaultCommunicationTimeouts.Instance.CloseTimeout; }
122 protected internal override TimeSpan DefaultOpenTimeout {
123 get { return DefaultCommunicationTimeouts.Instance.OpenTimeout; }
126 public TimeSpan CloseTimeout {
127 get { return close_timeout; }
128 set { close_timeout = value; }
131 public TimeSpan OpenTimeout {
132 get { return open_timeout; }
133 set { open_timeout = value; }
136 public int ManualFlowControlLimit {
137 get { return flow_limit; }
138 set { flow_limit = value; }
141 public ServiceEndpoint AddServiceEndpoint (
142 string implementedContract, Binding binding, string address)
144 return AddServiceEndpoint (implementedContract,
146 new Uri (address, UriKind.RelativeOrAbsolute));
149 public ServiceEndpoint AddServiceEndpoint (
150 string implementedContract, Binding binding,
151 string address, Uri listenUri)
153 Uri uri = new Uri (address, UriKind.RelativeOrAbsolute);
154 return AddServiceEndpoint (
155 implementedContract, binding, uri, uri);
158 public ServiceEndpoint AddServiceEndpoint (
159 string implementedContract, Binding binding,
162 return AddServiceEndpoint (implementedContract, binding, address, address);
165 public ServiceEndpoint AddServiceEndpoint (
166 string implementedContract, Binding binding,
167 Uri address, Uri listenUri)
169 EndpointAddress ea = BuildEndpointAddress (address, binding);
170 ContractDescription cd = GetContract (implementedContract);
172 throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
173 return AddServiceEndpointCore (cd, binding, ea, listenUri);
176 Type PopulateType (string typeName)
178 Type type = Type.GetType (typeName);
181 foreach (ContractDescription cd in ImplementedContracts.Values) {
182 type = cd.ContractType.Assembly.GetType (typeName);
189 ContractDescription GetContract (string typeName)
191 //FIXME: hack hack hack
192 ImplementedContracts ["IHttpGetHelpPageAndMetadataContract"] =
193 ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract));
195 // FIXME: As long as I tried, *only* IMetadataExchange
196 // is the exception case that does not require full
197 // type name. Hence I treat it as a special case.
198 if (typeName == ServiceMetadataBehavior.MexContractName) {
199 if (!Description.Behaviors.Contains (typeof (ServiceMetadataBehavior)) && Array.IndexOf (Description.ServiceType.GetInterfaces (), typeof (IMetadataExchange)) < 0)
200 throw new InvalidOperationException (
201 "Add ServiceMetadataBehavior to the ServiceHost to add a endpoint for IMetadataExchange contract.");
203 ImplementedContracts [ServiceMetadataBehavior.MexContractName] =
204 ContractDescription.GetContract (typeof (IMetadataExchange));
206 foreach (ContractDescription cd in ImplementedContracts.Values)
207 if (cd.ContractType == typeof (IMetadataExchange))
212 Type type = PopulateType (typeName);
216 foreach (ContractDescription cd in ImplementedContracts.Values) {
217 // FIXME: This check is a negative side effect
218 // of the above hack.
219 if (cd.ContractType == typeof (IMetadataExchange))
222 if (cd.ContractType == type ||
223 cd.ContractType.IsSubclassOf (type) ||
224 type.IsInterface && cd.ContractType.GetInterface (type.FullName) == type)
230 internal EndpointAddress BuildEndpointAddress (Uri address, Binding binding)
232 if (!address.IsAbsoluteUri) {
233 // Find a Base address with matching scheme,
234 // and build new absolute address
235 if (!base_addresses.Contains (binding.Scheme))
236 throw new InvalidOperationException (String.Format ("Could not find base address that matches Scheme {0} for endpoint {1}", binding.Scheme, binding.Name));
238 Uri baseaddr = base_addresses [binding.Scheme];
240 if (!baseaddr.AbsoluteUri.EndsWith ("/"))
241 baseaddr = new Uri (baseaddr.AbsoluteUri + "/");
242 address = new Uri (baseaddr, address);
244 return new EndpointAddress (address);
247 internal ServiceEndpoint AddServiceEndpointCore (
248 ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
250 foreach (ServiceEndpoint e in Description.Endpoints)
251 if (e.Contract == cd)
253 ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
254 se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
255 Description.Endpoints.Add (se);
260 protected virtual void ApplyConfiguration ()
262 if (Description == null)
263 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");
265 ServiceElement service = GetServiceElement ();
267 //TODO: Should we call here LoadServiceElement ?
268 if (service != null) {
271 HostElement host = service.Host;
272 foreach (BaseAddressElement baseAddress in host.BaseAddresses) {
273 this.base_addresses.Add (new Uri (baseAddress.BaseAddress));
277 foreach (ServiceEndpointElement endpoint in service.Endpoints) {
278 // FIXME: consider BindingName as well
279 ServiceEndpoint se = AddServiceEndpoint (
281 ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
282 endpoint.Address.ToString ());
285 // TODO: use EvaluationContext of ServiceElement.
286 ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors.Find (service.BehaviorConfiguration);
287 if (behavior != null) {
288 for (int i = 0; i < behavior.Count; i++) {
289 BehaviorExtensionElement bxel = behavior [i];
290 IServiceBehavior b = (IServiceBehavior) behavior [i].CreateBehavior ();
292 Description.Behaviors.Add (b);
296 // TODO: consider commonBehaviors here
298 // ensure ServiceAuthorizationBehavior
299 Authorization = Description.Behaviors.Find<ServiceAuthorizationBehavior> ();
300 if (Authorization == null) {
301 Authorization = new ServiceAuthorizationBehavior ();
302 Description.Behaviors.Add (Authorization);
305 // ensure ServiceDebugBehavior
306 ServiceDebugBehavior debugBehavior = Description.Behaviors.Find<ServiceDebugBehavior> ();
307 if (debugBehavior == null) {
308 debugBehavior = new ServiceDebugBehavior ();
309 Description.Behaviors.Add (debugBehavior);
313 private ServiceElement GetServiceElement() {
314 Type serviceType = Description.ServiceType;
315 if (serviceType == null)
318 return ConfigUtil.ServicesSection.Services [serviceType.FullName];
321 internal ContractDescription GetContract (string name, string ns)
323 foreach (ContractDescription d in ImplementedContracts.Values)
324 if (d.Name == name && d.Namespace == ns)
329 protected abstract ServiceDescription CreateDescription (
330 out IDictionary<string,ContractDescription> implementedContracts);
332 protected void InitializeDescription (UriSchemeKeyedCollection baseAddresses)
334 this.base_addresses = baseAddresses;
335 IDictionary<string,ContractDescription> retContracts;
336 description = CreateDescription (out retContracts);
337 contracts = retContracts;
339 ApplyConfiguration ();
342 protected virtual void InitializeRuntime ()
344 //First validate the description, which should call all behaviors
346 ValidateDescription ();
348 //Build all ChannelDispatchers, one dispatcher per user configured EndPoint.
349 //We must keep thet ServiceEndpoints as a seperate collection, since the user
350 //can change the collection in the description during the behaviors events.
351 Dictionary<ServiceEndpoint, ChannelDispatcher> endPointToDispatcher = new Dictionary<ServiceEndpoint,ChannelDispatcher>();
352 ServiceEndpoint[] endPoints = new ServiceEndpoint[Description.Endpoints.Count];
353 Description.Endpoints.CopyTo (endPoints, 0);
354 foreach (ServiceEndpoint se in endPoints) {
355 //Let all behaviors add their binding parameters
356 BindingParameterCollection commonParams =
357 new BindingParameterCollection ();
358 AddBindingParameters (commonParams, se);
359 ChannelDispatcher channel = BuildChannelDispatcher (se, commonParams);
360 ChannelDispatchers.Add (channel);
361 endPointToDispatcher[se] = channel;
364 //After the ChannelDispatchers are created, and attached to the service host
365 //Apply dispatching behaviors.
366 foreach (IServiceBehavior b in Description.Behaviors)
367 b.ApplyDispatchBehavior (Description, this);
369 foreach(KeyValuePair<ServiceEndpoint, ChannelDispatcher> val in endPointToDispatcher)
370 ApplyDispatchBehavior(val.Value, val.Key);
373 private void ValidateDescription ()
375 foreach (IServiceBehavior b in Description.Behaviors)
376 b.Validate (Description, this);
377 foreach (ServiceEndpoint endPoint in Description.Endpoints)
378 endPoint.Validate ();
381 private void ApplyDispatchBehavior (ChannelDispatcher dispatcher, ServiceEndpoint endPoint) {
382 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
383 b.ApplyDispatchBehavior (endPoint.Contract, endPoint, dispatcher.Endpoints[0].DispatchRuntime);
384 foreach (IEndpointBehavior b in endPoint.Behaviors)
385 b.ApplyDispatchBehavior (endPoint, dispatcher.Endpoints [0]);
386 foreach (OperationDescription operation in endPoint.Contract.Operations) {
387 foreach (IOperationBehavior b in operation.Behaviors)
388 b.ApplyDispatchBehavior (operation, dispatcher.Endpoints [0].DispatchRuntime.Operations [operation.Name]);
393 internal ChannelDispatcher BuildChannelDispatcher (ServiceEndpoint se, BindingParameterCollection commonParams) {
395 //User the binding parameters to build the channel listener and Dispatcher
396 IChannelListener lf = BuildListener (se, commonParams);
397 ChannelDispatcher cd = new ChannelDispatcher (
398 lf, se.Binding.Name);
399 cd.MessageVersion = se.Binding.MessageVersion;
400 if (cd.MessageVersion == null)
401 cd.MessageVersion = MessageVersion.Default;
403 //Attach one EndpointDispacher to the ChannelDispatcher
404 EndpointDispatcher endpoint_dispatcher =
405 new EndpointDispatcher (se.Address, se.Contract.Name, se.Contract.Namespace);
406 endpoint_dispatcher.DispatchRuntime.Type = Description.ServiceType;
407 endpoint_dispatcher.ContractFilter = GetContractFilter (se.Contract);
408 endpoint_dispatcher.ChannelDispatcher = cd;
409 cd.Endpoints.Add (endpoint_dispatcher);
411 //Build the dispatch operations
412 DispatchRuntime db = endpoint_dispatcher.DispatchRuntime;
413 foreach (OperationDescription od in se.Contract.Operations)
414 if (!db.Operations.Contains (od.Name))
415 PopulateDispatchOperation (db, od);
420 private MessageFilter GetContractFilter (ContractDescription contractDescription)
422 List<string> actions = new List<string> ();
423 foreach (OperationDescription od in contractDescription.Operations)
424 foreach (MessageDescription md in od.Messages)
425 if (md.Direction == MessageDirection.Input)
426 if (md.Action == "*")
427 return new MatchAllMessageFilter ();
429 actions.Add (md.Action);
431 return new ActionMessageFilter (actions.ToArray ());
434 private void AddBindingParameters (BindingParameterCollection commonParams, ServiceEndpoint endPoint) {
436 commonParams.Add (ChannelProtectionRequirements.CreateFromContract (endPoint.Contract));
437 foreach (IServiceBehavior b in Description.Behaviors)
438 b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
440 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
441 b.AddBindingParameters (endPoint.Contract, endPoint, commonParams);
442 foreach (IEndpointBehavior b in endPoint.Behaviors)
443 b.AddBindingParameters (endPoint, commonParams);
444 foreach (OperationDescription operation in endPoint.Contract.Operations) {
445 foreach (IOperationBehavior b in operation.Behaviors)
446 b.AddBindingParameters (operation, commonParams);
450 void PopulateDispatchOperation (DispatchRuntime db, OperationDescription od) {
451 string reqA = null, resA = null;
452 foreach (MessageDescription m in od.Messages) {
453 if (m.Direction == MessageDirection.Input)
458 DispatchOperation o =
460 new DispatchOperation (db, od.Name, reqA) :
461 new DispatchOperation (db, od.Name, reqA, resA);
462 bool has_void_reply = false;
463 foreach (MessageDescription md in od.Messages) {
464 if (md.Direction == MessageDirection.Input &&
465 md.Body.Parts.Count == 1 &&
466 md.Body.Parts [0].Type == typeof (Message))
467 o.DeserializeRequest = false;
468 if (md.Direction == MessageDirection.Output &&
469 md.Body.ReturnValue != null) {
470 if (md.Body.ReturnValue.Type == typeof (Message))
471 o.SerializeReply = false;
472 else if (md.Body.ReturnValue.Type == typeof (void))
473 has_void_reply = true;
478 // FIXME: support async method
479 if (od.SyncMethod != null)
480 o.Invoker = new SyncMethodInvoker (od.SyncMethod);
482 o.Invoker = new AsyncMethodInvoker (od.BeginMethod, od.EndMethod);
485 o.Formatter = BaseMessagesFormatter.Create (od);
487 if (o.Action == "*" && o.ReplyAction == "*") {
488 //Signature : Message (Message)
490 //FIXME: void (IChannel)
491 if (!o.DeserializeRequest && (!o.SerializeReply || has_void_reply))
492 db.UnhandledDispatchOperation = o;
495 db.Operations.Add (o);
499 protected void LoadConfigurationSection (ServiceElement element)
501 ServicesSection services = ConfigUtil.ServicesSection;
504 void DoOpen (TimeSpan timeout)
506 foreach (var cd in ChannelDispatchers) {
508 // This is likely hack.
509 if (cd is ChannelDispatcher)
510 ((ChannelDispatcher) cd).StartLoop ();
514 IChannelListener BuildListener (ServiceEndpoint se,
515 BindingParameterCollection pl)
517 Binding b = se.Binding;
518 if (b.CanBuildChannelListener<IReplySessionChannel> (pl))
519 return b.BuildChannelListener<IReplySessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
520 if (b.CanBuildChannelListener<IReplyChannel> (pl))
521 return b.BuildChannelListener<IReplyChannel> (se.ListenUri, "", se.ListenUriMode, pl);
522 if (b.CanBuildChannelListener<IInputSessionChannel> (pl))
523 return b.BuildChannelListener<IInputSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
524 if (b.CanBuildChannelListener<IInputChannel> (pl))
525 return b.BuildChannelListener<IInputChannel> (se.ListenUri, "", se.ListenUriMode, pl);
527 if (b.CanBuildChannelListener<IDuplexChannel> (pl))
528 return b.BuildChannelListener<IDuplexChannel> (se.ListenUri, "", se.ListenUriMode, pl);
529 if (b.CanBuildChannelListener<IDuplexSessionChannel> (pl))
530 return b.BuildChannelListener<IDuplexSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
531 throw new InvalidOperationException ("None of the listener channel types is supported");
535 protected override sealed void OnAbort ()
539 Action<TimeSpan> close_delegate;
540 Action<TimeSpan> open_delegate;
542 protected override sealed IAsyncResult OnBeginClose (
543 TimeSpan timeout, AsyncCallback callback, object state)
545 if (close_delegate != null)
546 close_delegate = new Action<TimeSpan> (OnClose);
547 return close_delegate.BeginInvoke (timeout, callback, state);
550 protected override sealed IAsyncResult OnBeginOpen (
551 TimeSpan timeout, AsyncCallback callback, object state)
553 if (open_delegate == null)
554 open_delegate = new Action<TimeSpan> (OnOpen);
555 return open_delegate.BeginInvoke (timeout, callback, state);
558 protected override void OnClose (TimeSpan timeout)
560 DateTime start = DateTime.Now;
561 ReleasePerformanceCounters ();
562 List<ChannelDispatcherBase> l = new List<ChannelDispatcherBase> (ChannelDispatchers);
563 foreach (ChannelDispatcherBase e in l) {
565 TimeSpan ts = timeout - (DateTime.Now - start);
566 if (ts < TimeSpan.Zero)
570 } catch (Exception ex) {
571 Console.WriteLine ("ServiceHostBase failed to close the channel dispatcher:");
572 Console.WriteLine (ex);
577 protected override sealed void OnOpen (TimeSpan timeout)
579 InitializeRuntime ();
583 protected override void OnEndClose (IAsyncResult result)
585 if (close_delegate == null)
586 throw new InvalidOperationException ("Async close operation has not started");
587 close_delegate.EndInvoke (result);
590 protected override sealed void OnEndOpen (IAsyncResult result)
592 if (open_delegate == null)
593 throw new InvalidOperationException ("Aync open operation has not started");
594 open_delegate.EndInvoke (result);
597 protected override void OnOpened ()
602 protected void ReleasePerformanceCounters ()
606 void IDisposable.Dispose ()
611 class SyncMethodInvoker : IOperationInvoker
613 readonly MethodInfo _methodInfo;
614 public SyncMethodInvoker (MethodInfo methodInfo) {
615 _methodInfo = methodInfo;
618 #region IOperationInvoker Members
620 public bool IsSynchronous {
624 public object [] AllocateParameters () {
625 return new object [_methodInfo.GetParameters ().Length];
628 public object Invoke (object instance, object [] parameters)
630 return _methodInfo.Invoke (instance, parameters);
633 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
634 throw new NotSupportedException ();
637 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result) {
638 throw new NotSupportedException ();
644 class AsyncMethodInvoker : IOperationInvoker
646 readonly MethodInfo _beginMethodInfo, _endMethodInfo;
647 public AsyncMethodInvoker (MethodInfo beginMethodInfo, MethodInfo endMethodInfo) {
648 _beginMethodInfo = beginMethodInfo;
649 _endMethodInfo = endMethodInfo;
652 #region IOperationInvoker Members
654 public bool IsSynchronous {
655 get { return false; }
658 public object [] AllocateParameters () {
659 return new object [_beginMethodInfo.GetParameters ().Length - 2 + _endMethodInfo.GetParameters().Length-1];
662 public object Invoke (object instance, object [] parameters) {
663 throw new NotImplementedException ("Can't invoke async method synchronously");
664 //BUGBUG: need to differentiate between input and output parameters.
665 IAsyncResult asyncResult = InvokeBegin(instance, parameters, delegate(IAsyncResult ignore) { }, null);
666 asyncResult.AsyncWaitHandle.WaitOne();
667 return InvokeEnd(instance, out parameters, asyncResult);
670 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
671 if (inputs.Length + 2 != _beginMethodInfo.GetParameters ().Length)
672 throw new ArgumentException ("Wrong number of input parameters");
673 object [] fullargs = new object [_beginMethodInfo.GetParameters ().Length];
674 Array.Copy (inputs, fullargs, inputs.Length);
675 fullargs [inputs.Length] = callback;
676 fullargs [inputs.Length + 1] = state;
677 return (IAsyncResult) _beginMethodInfo.Invoke (instance, fullargs);
680 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult asyncResult) {
681 outputs = new object [_endMethodInfo.GetParameters ().Length - 1];
682 object [] fullargs = new object [_endMethodInfo.GetParameters ().Length];
683 fullargs [outputs.Length] = asyncResult;
684 object result = _endMethodInfo.Invoke (instance, fullargs);
685 Array.Copy (fullargs, outputs, outputs.Length);