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 {
81 if (base_addresses == null)
82 base_addresses = new UriSchemeKeyedCollection ();
83 return new ReadOnlyCollection<Uri> (base_addresses.InternalItems);
87 internal Uri CreateUri (string sheme, Uri relatieUri) {
88 Uri baseUri = base_addresses.Contains (sheme) ? base_addresses [sheme] : null;
90 if (relatieUri == null)
92 if (relatieUri.IsAbsoluteUri)
96 return new Uri (baseUri, relatieUri);
99 public ChannelDispatcherCollection ChannelDispatchers {
100 get { return channel_dispatchers; }
103 public ServiceAuthorizationBehavior Authorization {
109 public ServiceCredentials Credentials {
110 get { return credentials; }
113 public ServiceDescription Description {
114 get { return description; }
117 protected IDictionary<string,ContractDescription> ImplementedContracts {
118 get { return contracts; }
122 public IExtensionCollection<ServiceHostBase> Extensions {
124 if (extensions == null)
125 extensions = new ExtensionCollection<ServiceHostBase> (this);
130 protected internal override TimeSpan DefaultCloseTimeout {
131 get { return DefaultCommunicationTimeouts.Instance.CloseTimeout; }
134 protected internal override TimeSpan DefaultOpenTimeout {
135 get { return DefaultCommunicationTimeouts.Instance.OpenTimeout; }
138 public TimeSpan CloseTimeout {
139 get { return close_timeout; }
140 set { close_timeout = value; }
143 public TimeSpan OpenTimeout {
144 get { return open_timeout; }
145 set { open_timeout = value; }
148 public int ManualFlowControlLimit {
149 get { return flow_limit; }
150 set { flow_limit = value; }
153 protected void AddBaseAddress (Uri baseAddress)
155 if (Description != null)
156 throw new InvalidOperationException ("Base addresses must be added before the service description is initialized");
157 base_addresses.Add (baseAddress);
160 public ServiceEndpoint AddServiceEndpoint (
161 string implementedContract, Binding binding, string address)
163 return AddServiceEndpoint (implementedContract,
165 new Uri (address, UriKind.RelativeOrAbsolute));
168 public ServiceEndpoint AddServiceEndpoint (
169 string implementedContract, Binding binding,
170 string address, Uri listenUri)
172 Uri uri = new Uri (address, UriKind.RelativeOrAbsolute);
173 return AddServiceEndpoint (
174 implementedContract, binding, uri, listenUri);
177 public ServiceEndpoint AddServiceEndpoint (
178 string implementedContract, Binding binding,
181 return AddServiceEndpoint (implementedContract, binding, address, address);
184 public ServiceEndpoint AddServiceEndpoint (
185 string implementedContract, Binding binding,
186 Uri address, Uri listenUri)
188 EndpointAddress ea = BuildEndpointAddress (address, binding);
189 ContractDescription cd = GetContract (implementedContract);
191 throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
192 return AddServiceEndpointCore (cd, binding, ea, listenUri);
195 Type PopulateType (string typeName)
197 Type type = Type.GetType (typeName);
200 foreach (ContractDescription cd in ImplementedContracts.Values) {
201 type = cd.ContractType.Assembly.GetType (typeName);
208 ContractDescription mex_contract, help_page_contract;
210 ContractDescription GetContract (string name)
212 // FIXME: not sure if they should really be special cases.
214 case "IHttpGetHelpPageAndMetadataContract":
215 if (help_page_contract == null)
216 help_page_contract = ContractDescription.GetContract (typeof (IHttpGetHelpPageAndMetadataContract));
217 return help_page_contract;
218 case "IMetadataExchange":
219 // this is certainly looking special (or we may
220 // be missing something around ServiceMetadataExtension)
221 if (Extensions.Find<ServiceMetadataExtension> () == null)
223 if (mex_contract == null)
224 mex_contract = ContractDescription.GetContract (typeof (IMetadataExchange));
228 // FIXME: probably type-to-contract-name mapping is wrong.
229 // This "loopup by type name" incorrectly allows
230 // "System.ServiceModel.Description.IMetadataExchange",
231 // but disabling this results in couple of regressions.
232 // So I keep enabling it so far. But it smells wrong.
233 Type type = PopulateType (name);
235 foreach (ContractDescription cd in ImplementedContracts.Values) {
242 // FIXME: This check is a negative side effect
243 // of the above hack. (but it should not still
244 // skip name-based match). Seealso above FIXMEs.
245 if (cd.ContractType == typeof (IMetadataExchange))
248 if (cd.ContractType == type ||
249 cd.ContractType.IsSubclassOf (type) ||
250 type.IsInterface && cd.ContractType.GetInterface (type.FullName) == type)
256 internal EndpointAddress BuildEndpointAddress (Uri address, Binding binding)
258 if (!address.IsAbsoluteUri) {
259 // Find a Base address with matching scheme,
260 // and build new absolute address
261 if (!base_addresses.Contains (binding.Scheme))
262 throw new InvalidOperationException (String.Format ("Could not find base address that matches Scheme {0} for endpoint {1}", binding.Scheme, binding.Name));
264 Uri baseaddr = base_addresses [binding.Scheme];
266 if (!baseaddr.AbsoluteUri.EndsWith ("/") && address.OriginalString.Length > 0) // with empty URI it should not add '/' to possible file name of the absolute URI
267 baseaddr = new Uri (baseaddr.AbsoluteUri + "/");
268 address = new Uri (baseaddr, address);
270 return new EndpointAddress (address);
273 internal ServiceEndpoint AddServiceEndpointCore (
274 ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
276 foreach (ServiceEndpoint e in Description.Endpoints)
277 if (e.Contract == cd)
279 ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
280 se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
281 Description.Endpoints.Add (se);
286 protected virtual void ApplyConfiguration ()
288 if (Description == null)
289 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");
291 ServiceElement service = GetServiceElement ();
293 //TODO: Should we call here LoadServiceElement ?
294 if (service != null) {
297 HostElement host = service.Host;
298 foreach (BaseAddressElement baseAddress in host.BaseAddresses) {
299 AddBaseAddress (new Uri (baseAddress.BaseAddress));
303 // TODO: use EvaluationContext of ServiceElement.
304 ServiceBehaviorElement behavior = ConfigUtil.BehaviorsSection.ServiceBehaviors [service.BehaviorConfiguration];
305 if (behavior != null) {
306 foreach (var bxe in behavior) {
307 IServiceBehavior b = (IServiceBehavior) bxe.CreateBehavior ();
308 Description.Behaviors.Add (b);
313 foreach (ServiceEndpointElement endpoint in service.Endpoints) {
314 // FIXME: consider BindingName as well
315 ServiceEndpoint se = AddServiceEndpoint (
317 ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
318 endpoint.Address.ToString ());
319 // endpoint behaviors
320 EndpointBehaviorElement epbehavior = ConfigUtil.BehaviorsSection.EndpointBehaviors [endpoint.BehaviorConfiguration];
321 if (epbehavior != null)
322 foreach (var bxe in epbehavior) {
323 IEndpointBehavior b = (IEndpointBehavior) bxe.CreateBehavior ();
324 se.Behaviors.Add (b);
328 // TODO: consider commonBehaviors here
330 // ensure ServiceAuthorizationBehavior
331 Authorization = Description.Behaviors.Find<ServiceAuthorizationBehavior> ();
332 if (Authorization == null) {
333 Authorization = new ServiceAuthorizationBehavior ();
334 Description.Behaviors.Add (Authorization);
337 // ensure ServiceDebugBehavior
338 ServiceDebugBehavior debugBehavior = Description.Behaviors.Find<ServiceDebugBehavior> ();
339 if (debugBehavior == null) {
340 debugBehavior = new ServiceDebugBehavior ();
341 Description.Behaviors.Add (debugBehavior);
345 private ServiceElement GetServiceElement() {
346 Type serviceType = Description.ServiceType;
347 if (serviceType == null)
350 return ConfigUtil.ServicesSection.Services [serviceType.FullName];
353 protected abstract ServiceDescription CreateDescription (
354 out IDictionary<string,ContractDescription> implementedContracts);
356 protected void InitializeDescription (UriSchemeKeyedCollection baseAddresses)
358 this.base_addresses = baseAddresses;
359 IDictionary<string,ContractDescription> retContracts;
360 description = CreateDescription (out retContracts);
361 contracts = retContracts;
363 ApplyConfiguration ();
366 protected virtual void InitializeRuntime ()
368 //First validate the description, which should call all behaviors
370 ValidateDescription ();
372 //Build all ChannelDispatchers, one dispatcher per user configured EndPoint.
373 //We must keep thet ServiceEndpoints as a seperate collection, since the user
374 //can change the collection in the description during the behaviors events.
375 Dictionary<ServiceEndpoint, ChannelDispatcher> endPointToDispatcher = new Dictionary<ServiceEndpoint,ChannelDispatcher>();
376 ServiceEndpoint[] endPoints = new ServiceEndpoint[Description.Endpoints.Count];
377 Description.Endpoints.CopyTo (endPoints, 0);
378 foreach (ServiceEndpoint se in endPoints) {
380 var commonParams = new BindingParameterCollection ();
381 foreach (IServiceBehavior b in Description.Behaviors)
382 b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
384 ChannelDispatcher channel = BuildChannelDispatcher (se, commonParams);
385 ChannelDispatchers.Add (channel);
386 endPointToDispatcher[se] = channel;
389 //After the ChannelDispatchers are created, and attached to the service host
390 //Apply dispatching behaviors.
391 foreach (IServiceBehavior b in Description.Behaviors)
392 b.ApplyDispatchBehavior (Description, this);
394 foreach(KeyValuePair<ServiceEndpoint, ChannelDispatcher> val in endPointToDispatcher)
395 foreach (var ed in val.Value.Endpoints)
396 ApplyDispatchBehavior (ed, val.Key);
399 private void ValidateDescription ()
401 foreach (IServiceBehavior b in Description.Behaviors)
402 b.Validate (Description, this);
403 foreach (ServiceEndpoint endPoint in Description.Endpoints)
404 endPoint.Validate ();
407 private void ApplyDispatchBehavior (EndpointDispatcher ed, ServiceEndpoint endPoint)
409 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
410 b.ApplyDispatchBehavior (endPoint.Contract, endPoint, ed.DispatchRuntime);
411 foreach (IEndpointBehavior b in endPoint.Behaviors)
412 b.ApplyDispatchBehavior (endPoint, ed);
413 foreach (OperationDescription operation in endPoint.Contract.Operations) {
414 foreach (IOperationBehavior b in operation.Behaviors)
415 b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
420 internal ChannelDispatcher BuildChannelDispatcher (ServiceEndpoint se, BindingParameterCollection commonParams)
422 return new DispatcherBuilder ().BuildChannelDispatcher (Description.ServiceType, se, commonParams);
426 protected void LoadConfigurationSection (ServiceElement element)
428 ServicesSection services = ConfigUtil.ServicesSection;
431 void DoOpen (TimeSpan timeout)
433 foreach (var cd in ChannelDispatchers) {
435 // This is likely hack.
436 if (cd is ChannelDispatcher)
437 ((ChannelDispatcher) cd).StartLoop ();
442 protected override sealed void OnAbort ()
446 Action<TimeSpan> close_delegate;
447 Action<TimeSpan> open_delegate;
449 protected override sealed IAsyncResult OnBeginClose (
450 TimeSpan timeout, AsyncCallback callback, object state)
452 if (close_delegate != null)
453 close_delegate = new Action<TimeSpan> (OnClose);
454 return close_delegate.BeginInvoke (timeout, callback, state);
457 protected override sealed IAsyncResult OnBeginOpen (
458 TimeSpan timeout, AsyncCallback callback, object state)
460 if (open_delegate == null)
461 open_delegate = new Action<TimeSpan> (OnOpen);
462 return open_delegate.BeginInvoke (timeout, callback, state);
465 protected override void OnClose (TimeSpan timeout)
467 DateTime start = DateTime.Now;
468 ReleasePerformanceCounters ();
469 List<ChannelDispatcherBase> l = new List<ChannelDispatcherBase> (ChannelDispatchers);
470 foreach (ChannelDispatcherBase e in l) {
472 TimeSpan ts = timeout - (DateTime.Now - start);
473 if (ts < TimeSpan.Zero)
477 } catch (Exception ex) {
478 Console.WriteLine ("ServiceHostBase failed to close the channel dispatcher:");
479 Console.WriteLine (ex);
484 protected override sealed void OnOpen (TimeSpan timeout)
486 InitializeRuntime ();
490 protected override void OnEndClose (IAsyncResult result)
492 if (close_delegate == null)
493 throw new InvalidOperationException ("Async close operation has not started");
494 close_delegate.EndInvoke (result);
497 protected override sealed void OnEndOpen (IAsyncResult result)
499 if (open_delegate == null)
500 throw new InvalidOperationException ("Aync open operation has not started");
501 open_delegate.EndInvoke (result);
504 protected override void OnOpened ()
510 protected void ReleasePerformanceCounters ()
514 void IDisposable.Dispose ()
520 class SyncMethodInvoker : IOperationInvoker
522 readonly MethodInfo _methodInfo;
523 public SyncMethodInvoker (MethodInfo methodInfo) {
524 _methodInfo = methodInfo;
527 #region IOperationInvoker Members
529 public bool IsSynchronous {
533 public object [] AllocateParameters () {
534 return new object [_methodInfo.GetParameters ().Length];
537 public object Invoke (object instance, object [] parameters)
539 return _methodInfo.Invoke (instance, parameters);
542 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
543 throw new NotSupportedException ();
546 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult result) {
547 throw new NotSupportedException ();
553 class AsyncMethodInvoker : IOperationInvoker
555 readonly MethodInfo _beginMethodInfo, _endMethodInfo;
556 public AsyncMethodInvoker (MethodInfo beginMethodInfo, MethodInfo endMethodInfo) {
557 _beginMethodInfo = beginMethodInfo;
558 _endMethodInfo = endMethodInfo;
561 #region IOperationInvoker Members
563 public bool IsSynchronous {
564 get { return false; }
567 public object [] AllocateParameters () {
568 return new object [_beginMethodInfo.GetParameters ().Length - 2 + _endMethodInfo.GetParameters().Length-1];
571 public object Invoke (object instance, object [] parameters) {
572 throw new NotImplementedException ("Can't invoke async method synchronously");
573 //BUGBUG: need to differentiate between input and output parameters.
574 IAsyncResult asyncResult = InvokeBegin(instance, parameters, delegate(IAsyncResult ignore) { }, null);
575 asyncResult.AsyncWaitHandle.WaitOne();
576 return InvokeEnd(instance, out parameters, asyncResult);
579 public IAsyncResult InvokeBegin (object instance, object [] inputs, AsyncCallback callback, object state) {
580 if (inputs.Length + 2 != _beginMethodInfo.GetParameters ().Length)
581 throw new ArgumentException ("Wrong number of input parameters");
582 object [] fullargs = new object [_beginMethodInfo.GetParameters ().Length];
583 Array.Copy (inputs, fullargs, inputs.Length);
584 fullargs [inputs.Length] = callback;
585 fullargs [inputs.Length + 1] = state;
586 return (IAsyncResult) _beginMethodInfo.Invoke (instance, fullargs);
589 public object InvokeEnd (object instance, out object [] outputs, IAsyncResult asyncResult) {
590 outputs = new object [_endMethodInfo.GetParameters ().Length - 1];
591 object [] fullargs = new object [_endMethodInfo.GetParameters ().Length];
592 fullargs [outputs.Length] = asyncResult;
593 object result = _endMethodInfo.Invoke (instance, fullargs);
594 Array.Copy (fullargs, outputs, outputs.Length);
603 partial class DispatcherBuilder
605 internal ChannelDispatcher BuildChannelDispatcher (Type serviceType, ServiceEndpoint se, BindingParameterCollection commonParams)
607 //Let all behaviors add their binding parameters
608 AddBindingParameters (commonParams, se);
609 //User the binding parameters to build the channel listener and Dispatcher
610 IChannelListener lf = BuildListener (se, commonParams);
611 ChannelDispatcher cd = new ChannelDispatcher (
612 lf, se.Binding.Name);
613 cd.InitializeServiceEndpoint (serviceType, se);
617 private void AddBindingParameters (BindingParameterCollection commonParams, ServiceEndpoint endPoint) {
619 commonParams.Add (ChannelProtectionRequirements.CreateFromContract (endPoint.Contract));
621 foreach (IContractBehavior b in endPoint.Contract.Behaviors)
622 b.AddBindingParameters (endPoint.Contract, endPoint, commonParams);
623 foreach (IEndpointBehavior b in endPoint.Behaviors)
624 b.AddBindingParameters (endPoint, commonParams);
625 foreach (OperationDescription operation in endPoint.Contract.Operations) {
626 foreach (IOperationBehavior b in operation.Behaviors)
627 b.AddBindingParameters (operation, commonParams);
631 static IChannelListener BuildListener (ServiceEndpoint se,
632 BindingParameterCollection pl)
634 Binding b = se.Binding;
635 if (b.CanBuildChannelListener<IReplySessionChannel> (pl))
636 return b.BuildChannelListener<IReplySessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
637 if (b.CanBuildChannelListener<IReplyChannel> (pl))
638 return b.BuildChannelListener<IReplyChannel> (se.ListenUri, "", se.ListenUriMode, pl);
639 if (b.CanBuildChannelListener<IInputSessionChannel> (pl))
640 return b.BuildChannelListener<IInputSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
641 if (b.CanBuildChannelListener<IInputChannel> (pl))
642 return b.BuildChannelListener<IInputChannel> (se.ListenUri, "", se.ListenUriMode, pl);
644 if (b.CanBuildChannelListener<IDuplexChannel> (pl))
645 return b.BuildChannelListener<IDuplexChannel> (se.ListenUri, "", se.ListenUriMode, pl);
646 if (b.CanBuildChannelListener<IDuplexSessionChannel> (pl))
647 return b.BuildChannelListener<IDuplexSessionChannel> (se.ListenUri, "", se.ListenUriMode, pl);
648 throw new InvalidOperationException ("None of the listener channel types is supported");