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));
150 public ServiceEndpoint AddServiceEndpoint (
151 string implementedContract, Binding binding,
152 string address, Uri listenUri)
154 Uri uri = new Uri (address, UriKind.RelativeOrAbsolute);
155 return AddServiceEndpoint (
156 implementedContract, binding, uri, uri);
160 public ServiceEndpoint AddServiceEndpoint (
161 string implementedContract, Binding binding,
164 return AddServiceEndpoint (implementedContract, binding, address, address);
168 public ServiceEndpoint AddServiceEndpoint (
169 string implementedContract, Binding binding,
170 Uri address, Uri listenUri)
172 EndpointAddress ea = BuildEndpointAddress (address, binding);
173 ContractDescription cd = GetContract (implementedContract);
175 throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
176 return AddServiceEndpointCore (cd, binding, ea, listenUri);
179 Type PopulateType (string typeName)
181 Type type = Type.GetType (typeName);
184 foreach (ContractDescription cd in ImplementedContracts.Values) {
185 type = cd.ContractType.Assembly.GetType (typeName);
192 ContractDescription GetContract (string typeName)
194 //FIXME: hack hack hack
195 ImplementedContracts ["IHttpGetHelpPageAndMetadataContract"] =
196 ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract));
198 // FIXME: As long as I tried, *only* IMetadataExchange
199 // is the exception case that does not require full
200 // type name. Hence I treat it as a special case.
201 if (typeName == ServiceMetadataBehavior.MexContractName) {
202 if (!Description.Behaviors.Contains (typeof (ServiceMetadataBehavior)) && Array.IndexOf (Description.ServiceType.GetInterfaces (), typeof (IMetadataExchange)) < 0)
203 throw new InvalidOperationException (
204 "Add ServiceMetadataBehavior to the ServiceHost to add a endpoint for IMetadataExchange contract.");
206 ImplementedContracts [ServiceMetadataBehavior.MexContractName] =
207 ContractDescription.GetContract (typeof (IMetadataExchange));
209 foreach (ContractDescription cd in ImplementedContracts.Values)
210 if (cd.ContractType == typeof (IMetadataExchange))
215 Type type = PopulateType (typeName);
219 foreach (ContractDescription cd in ImplementedContracts.Values) {
220 // FIXME: This check is a negative side effect
221 // of the above hack.
222 if (cd.ContractType == typeof (IMetadataExchange))
225 if (cd.ContractType == type ||
226 cd.ContractType.IsSubclassOf (type) ||
227 type.IsInterface && cd.ContractType.GetInterface (type.FullName) == type)
233 internal EndpointAddress BuildEndpointAddress (Uri address, Binding binding)
235 if (!address.IsAbsoluteUri) {
236 // Find a Base address with matching scheme,
237 // and build new absolute address
238 if (!base_addresses.Contains (binding.Scheme))
239 throw new InvalidOperationException (String.Format ("Could not find base address that matches Scheme {0} for endpoint {1}", binding.Scheme, binding.Name));
241 Uri baseaddr = base_addresses [binding.Scheme];
243 if (!baseaddr.AbsoluteUri.EndsWith ("/"))
244 baseaddr = new Uri (baseaddr.AbsoluteUri + "/");
245 address = new Uri (baseaddr, address);
247 return new EndpointAddress (address);
250 internal ServiceEndpoint AddServiceEndpointCore (
251 ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
253 foreach (ServiceEndpoint e in Description.Endpoints)
254 if (e.Contract == cd)
256 ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
257 se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
258 Description.Endpoints.Add (se);
263 protected virtual void ApplyConfiguration ()
265 if (Description == null)
266 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");
268 ServiceElement service = GetServiceElement ();
270 //TODO: Should we call here LoadServiceElement ?
271 if (service != null) {
274 HostElement host = service.Host;
275 foreach (BaseAddressElement baseAddress in host.BaseAddresses) {
276 this.base_addresses.Add (new Uri (baseAddress.BaseAddress));
280 foreach (ServiceEndpointElement endpoint in service.Endpoints) {
281 // FIXME: consider BindingName as well
282 ServiceEndpoint se = AddServiceEndpoint (
284 ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
285 endpoint.Address.ToString ());
288 // TODO: use EvaluationContext of ServiceElement.
289 ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors.Find (service.BehaviorConfiguration);
290 if (behavior != null) {
291 for (int i = 0; i < behavior.Count; i++) {
292 BehaviorExtensionElement bxel = behavior [i];
293 IServiceBehavior b = (IServiceBehavior) behavior [i].CreateBehavior ();
295 Description.Behaviors.Add (b);
299 // TODO: consider commonBehaviors here
301 // ensure ServiceAuthorizationBehavior
302 Authorization = Description.Behaviors.Find<ServiceAuthorizationBehavior> ();
303 if (Authorization == null) {
304 Authorization = new ServiceAuthorizationBehavior ();
305 Description.Behaviors.Add (Authorization);
308 // ensure ServiceDebugBehavior
309 ServiceDebugBehavior debugBehavior = Description.Behaviors.Find<ServiceDebugBehavior> ();
310 if (debugBehavior == null) {
311 debugBehavior = new ServiceDebugBehavior ();
312 Description.Behaviors.Add (debugBehavior);
316 private ServiceElement GetServiceElement() {
317 Type serviceType = Description.ServiceType;
318 if (serviceType == null)
321 return ConfigUtil.ServicesSection.Services [serviceType.FullName];
324 internal ContractDescription GetContract (string name, string ns)
326 foreach (ContractDescription d in ImplementedContracts.Values)
327 if (d.Name == name && d.Namespace == ns)
332 protected abstract ServiceDescription CreateDescription (
333 out IDictionary<string,ContractDescription> implementedContracts);
336 protected void InitializeDescription (UriSchemeKeyedCollection baseAddresses)
338 this.base_addresses = baseAddresses;
339 IDictionary<string,ContractDescription> retContracts;
340 description = CreateDescription (out retContracts);
341 contracts = retContracts;
343 ApplyConfiguration ();
347 protected virtual void InitializeRuntime ()
349 //First validate the description, which should call all behaviors
351 ValidateDescription ();
353 //Build all ChannelDispatchers, one dispatcher per user configured EndPoint.
354 //We must keep thet ServiceEndpoints as a seperate collection, since the user
355 //can change the collection in the description during the behaviors events.
356 Dictionary<ServiceEndpoint, ChannelDispatcher> endPointToDispatcher = new Dictionary<ServiceEndpoint,ChannelDispatcher>();
357 ServiceEndpoint[] endPoints = new ServiceEndpoint[Description.Endpoints.Count];
358 Description.Endpoints.CopyTo (endPoints, 0);
359 foreach (ServiceEndpoint se in endPoints) {
360 //Let all behaviors add their binding parameters
361 BindingParameterCollection commonParams =
362 new BindingParameterCollection ();
363 AddBindingParameters (commonParams, se);
364 ChannelDispatcher channel = BuildChannelDispatcher (se, commonParams);
365 ChannelDispatchers.Add (channel);
366 endPointToDispatcher[se] = channel;
369 //After the ChannelDispatchers are created, and attached to the service host
370 //Apply dispatching behaviors.
371 foreach (IServiceBehavior b in Description.Behaviors)
372 b.ApplyDispatchBehavior (Description, this);
374 foreach(KeyValuePair<ServiceEndpoint, ChannelDispatcher> val in endPointToDispatcher)
375 ApplyDispatchBehavior(val.Value, val.Key);
378 private void ValidateDescription () {
379 foreach (IServiceBehavior b in Description.Behaviors)
380 b.Validate (Description, this);
381 foreach (ServiceEndpoint endPoint in Description.Endpoints)
382 endPoint.Validate ();
385 private void ApplyDispatchBehavior (ChannelDispatcher dispatcher, ServiceEndpoint endPoint) {
386 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
387 b.ApplyDispatchBehavior (endPoint.Contract, endPoint, dispatcher.Endpoints[0].DispatchRuntime);
388 foreach (IEndpointBehavior b in endPoint.Behaviors)
389 b.ApplyDispatchBehavior (endPoint, dispatcher.Endpoints [0]);
390 foreach (OperationDescription operation in endPoint.Contract.Operations) {
391 foreach (IOperationBehavior b in operation.Behaviors)
392 b.ApplyDispatchBehavior (operation, dispatcher.Endpoints [0].DispatchRuntime.Operations [operation.Name]);
397 internal ChannelDispatcher BuildChannelDispatcher (ServiceEndpoint se, BindingParameterCollection commonParams) {
399 //User the binding parameters to build the channel listener and Dispatcher
400 IChannelListener lf = BuildListener (se, commonParams);
401 ChannelDispatcher cd = new ChannelDispatcher (
402 lf, se.Binding.Name);
403 cd.MessageVersion = se.Binding.MessageVersion;
404 if (cd.MessageVersion == null)
405 cd.MessageVersion = MessageVersion.Default;
407 //Attach one EndpointDispacher to the ChannelDispatcher
408 EndpointDispatcher endpoint_dispatcher =
409 new EndpointDispatcher (se.Address, se.Contract.Name, se.Contract.Namespace);
410 endpoint_dispatcher.DispatchRuntime.Type = Description.ServiceType;
411 endpoint_dispatcher.ContractFilter = GetContractFilter (se.Contract);
412 endpoint_dispatcher.ChannelDispatcher = cd;
413 cd.Endpoints.Add (endpoint_dispatcher);
415 //Build the dispatch operations
416 DispatchRuntime db = endpoint_dispatcher.DispatchRuntime;
417 foreach (OperationDescription od in se.Contract.Operations)
418 if (!db.Operations.Contains (od.Name))
419 PopulateDispatchOperation (db, od);
424 private MessageFilter GetContractFilter (ContractDescription contractDescription)
426 List<string> actions = new List<string> ();
427 foreach (OperationDescription od in contractDescription.Operations)
428 foreach (MessageDescription md in od.Messages)
429 if (md.Direction == MessageDirection.Input)
430 if (md.Action == "*")
431 return new MatchAllMessageFilter ();
433 actions.Add (md.Action);
435 return new ActionMessageFilter (actions.ToArray ());
438 private void AddBindingParameters (BindingParameterCollection commonParams, ServiceEndpoint endPoint) {
440 commonParams.Add (ChannelProtectionRequirements.CreateFromContract (endPoint.Contract));
441 foreach (IServiceBehavior b in Description.Behaviors)
442 b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
444 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
445 b.AddBindingParameters (endPoint.Contract, endPoint, commonParams);
446 foreach (IEndpointBehavior b in endPoint.Behaviors)
447 b.AddBindingParameters (endPoint, commonParams);
448 foreach (OperationDescription operation in endPoint.Contract.Operations) {
449 foreach (IOperationBehavior b in operation.Behaviors)
450 b.AddBindingParameters (operation, commonParams);
454 void PopulateDispatchOperation (DispatchRuntime db, OperationDescription od) {
455 string reqA = null, resA = null;
456 foreach (MessageDescription m in od.Messages) {
457 if (m.Direction == MessageDirection.Input)
462 DispatchOperation o =
464 new DispatchOperation (db, od.Name, reqA) :
465 new DispatchOperation (db, od.Name, reqA, resA);
466 bool has_void_reply = false;
467 foreach (MessageDescription md in od.Messages) {
468 if (md.Direction == MessageDirection.Input &&
469 md.Body.Parts.Count == 1 &&
470 md.Body.Parts [0].Type == typeof (Message))
471 o.DeserializeRequest = false;
472 if (md.Direction == MessageDirection.Output &&
473 md.Body.ReturnValue != null) {
474 if (md.Body.ReturnValue.Type == typeof (Message))
475 o.SerializeReply = false;
476 else if (md.Body.ReturnValue.Type == typeof (void))
477 has_void_reply = true;
482 // FIXME: support async method
483 if (od.SyncMethod != null)
484 o.Invoker = new SyncMethodInvoker (od.SyncMethod);
486 o.Invoker = new AsyncMethodInvoker (od.BeginMethod, od.EndMethod);
489 o.Formatter = BaseMessagesFormatter.Create (od);
491 if (o.Action == "*" && o.ReplyAction == "*") {
492 //Signature : Message (Message)
494 //FIXME: void (IChannel)
495 if (!o.DeserializeRequest && (!o.SerializeReply || has_void_reply))
496 db.UnhandledDispatchOperation = o;
499 db.Operations.Add (o);
503 protected void LoadConfigurationSection (ServiceElement element)
505 ServicesSection services = ConfigUtil.ServicesSection;
508 void DoOpen (TimeSpan timeout)
510 foreach (var cd in ChannelDispatchers) {
512 // This is likely hack.
513 if (cd is ChannelDispatcher)
514 ((ChannelDispatcher) cd).StartLoop ();
518 IChannelListener BuildListener (ServiceEndpoint se,
519 BindingParameterCollection pl)
521 Binding b = se.Binding;
522 if (b.CanBuildChannelListener<IReplySessionChannel> (pl))
523 return b.BuildChannelListener<IReplySessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
524 if (b.CanBuildChannelListener<IReplyChannel> (pl))
525 return b.BuildChannelListener<IReplyChannel> (se.ListenUri, "", se.ListenUriMode, pl);
526 if (b.CanBuildChannelListener<IInputSessionChannel> (pl))
527 return b.BuildChannelListener<IInputSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
528 if (b.CanBuildChannelListener<IInputChannel> (pl))
529 return b.BuildChannelListener<IInputChannel> (se.ListenUri, "", se.ListenUriMode, pl);
531 if (b.CanBuildChannelListener<IDuplexChannel> (pl))
532 return b.BuildChannelListener<IDuplexChannel> (se.ListenUri, "", se.ListenUriMode, pl);
533 if (b.CanBuildChannelListener<IDuplexSessionChannel> (pl))
534 return b.BuildChannelListener<IDuplexSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
535 throw new InvalidOperationException ("None of the listener channel types is supported");
539 protected override sealed void OnAbort ()
544 protected override sealed IAsyncResult OnBeginClose (
545 TimeSpan timeout, AsyncCallback callback, object state)
547 throw new NotImplementedException ();
551 protected override sealed IAsyncResult OnBeginOpen (
552 TimeSpan timeout, AsyncCallback callback, object state)
554 throw new NotImplementedException ();
557 protected override void OnClose (TimeSpan timeout)
559 DateTime start = DateTime.Now;
560 ReleasePerformanceCounters ();
561 List<ChannelDispatcherBase> l = new List<ChannelDispatcherBase> (ChannelDispatchers);
562 foreach (ChannelDispatcherBase e in l) {
564 TimeSpan ts = timeout - (DateTime.Now - start);
565 if (ts < TimeSpan.Zero)
569 } catch (Exception ex) {
570 Console.WriteLine ("ServiceHostBase failed to close the channel dispatcher:");
571 Console.WriteLine (ex);
576 protected override sealed void OnOpen (TimeSpan timeout)
578 InitializeRuntime ();
583 protected override void OnEndClose (IAsyncResult result)
585 throw new NotImplementedException ();
589 protected override sealed void OnEndOpen (IAsyncResult result)
591 throw new NotImplementedException ();
594 protected override void OnOpened ()
599 protected void ReleasePerformanceCounters ()
603 void IDisposable.Dispose ()
608 class SyncMethodInvoker : IOperationInvoker
610 readonly MethodInfo _methodInfo;
611 public SyncMethodInvoker (MethodInfo methodInfo) {
612 _methodInfo = methodInfo;
615 #region IOperationInvoker Members
617 public bool IsSynchronous {
621 public object [] AllocateParameters () {
622 return new object [_methodInfo.GetParameters ().Length];
625 public object Invoke (object instance, object [] parameters)
627 return _methodInfo.Invoke (instance, parameters);
630 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
631 throw new NotSupportedException ();
634 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result) {
635 throw new NotSupportedException ();
641 class AsyncMethodInvoker : IOperationInvoker
643 readonly MethodInfo _beginMethodInfo, _endMethodInfo;
644 public AsyncMethodInvoker (MethodInfo beginMethodInfo, MethodInfo endMethodInfo) {
645 _beginMethodInfo = beginMethodInfo;
646 _endMethodInfo = endMethodInfo;
649 #region IOperationInvoker Members
651 public bool IsSynchronous {
652 get { return false; }
655 public object [] AllocateParameters () {
656 return new object [_beginMethodInfo.GetParameters ().Length - 2 + _endMethodInfo.GetParameters().Length-1];
659 public object Invoke (object instance, object [] parameters) {
660 throw new NotImplementedException ("Can't invoke async method synchronously");
661 //BUGBUG: need to differentiate between input and output parameters.
662 IAsyncResult asyncResult = InvokeBegin(instance, parameters, delegate(IAsyncResult ignore) { }, null);
663 asyncResult.AsyncWaitHandle.WaitOne();
664 return InvokeEnd(instance, out parameters, asyncResult);
667 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
668 if (inputs.Length + 2 != _beginMethodInfo.GetParameters ().Length)
669 throw new ArgumentException ("Wrong number of input parameters");
670 object [] fullargs = new object [_beginMethodInfo.GetParameters ().Length];
671 Array.Copy (inputs, fullargs, inputs.Length);
672 fullargs [inputs.Length] = callback;
673 fullargs [inputs.Length + 1] = state;
674 return (IAsyncResult) _beginMethodInfo.Invoke (instance, fullargs);
677 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult asyncResult) {
678 outputs = new object [_endMethodInfo.GetParameters ().Length - 1];
679 object [] fullargs = new object [_endMethodInfo.GetParameters ().Length];
680 fullargs [outputs.Length] = asyncResult;
681 object result = _endMethodInfo.Invoke (instance, fullargs);
682 Array.Copy (fullargs, outputs, outputs.Length);