New tests.
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel / ServiceHostBase.cs
index fe1ac72895fc5cf7be08af67e64f620b37c51452..81b5608aabdaa9e6acbd19776e59d7d5e5c23a3a 100644 (file)
@@ -41,6 +41,9 @@ namespace System.ServiceModel
        public abstract partial class ServiceHostBase
                : CommunicationObject, IExtensibleObject<ServiceHostBase>, IDisposable
        {
+               // It is used for mapping a ServiceHostBase to HttpChannelListener precisely.
+               internal static ServiceHostBase CurrentServiceHostHack;
+
                ServiceCredentials credentials;
                ServiceDescription description;
                UriSchemeKeyedCollection base_addresses;
@@ -197,7 +200,7 @@ namespace System.ServiceModel
                        Uri address, Uri listenUri)
                {
                        EndpointAddress ea = BuildEndpointAddress (address, binding);
-                       ContractDescription cd = GetContract (implementedContract);
+                       ContractDescription cd = GetContract (implementedContract, binding.Namespace == "http://schemas.microsoft.com/ws/2005/02/mex/bindings");
                        if (cd == null)
                                throw new InvalidOperationException (String.Format ("Contract '{0}' was not found in the implemented contracts in this service host.", implementedContract));
                        return AddServiceEndpointCore (cd, binding, ea, listenUri);
@@ -218,7 +221,7 @@ namespace System.ServiceModel
 
                ContractDescription mex_contract, help_page_contract;
 
-               ContractDescription GetContract (string name)
+               ContractDescription GetContract (string name, bool mexBinding)
                {
                        // FIXME: not sure if they should really be special cases.
                        switch (name) {
@@ -234,30 +237,19 @@ namespace System.ServiceModel
                                // if it was added only IMetadataExchange 
                                // endpoint (and you'll see the word
                                // "infrastructure" in the exception message).
-                               if (Description.Behaviors.Find<ServiceMetadataBehavior> () == null)
+                               if (mexBinding && Description.Behaviors.Find<ServiceMetadataBehavior> () == null)
                                        break;
                                if (mex_contract == null)
                                        mex_contract = ContractDescription.GetContract (typeof (IMetadataExchange));
                                return mex_contract;
                        }
 
-                       // FIXME: probably type-to-contract-name mapping is wrong. 
-                       // This "loopup by type name" incorrectly allows
-                       // "System.ServiceModel.Description.IMetadataExchange",
-                       // but disabling this results in couple of regressions.
-                       // So I keep enabling it so far. But it smells wrong.
                        Type type = PopulateType (name);
+                       if (type == null)
+                               return null;
 
                        foreach (ContractDescription cd in ImplementedContracts.Values) {
-                               if (type == null) {
-                                       if (cd.Name == name)
-                                               return cd;
-                                       continue;
-                               }
-
-                               // FIXME: This check is a negative side effect 
-                               // of the above hack. (but it should not still 
-                               // skip name-based match). Seealso above FIXMEs.
+                               // This check is a negative side effect of the above match-by-name design.
                                if (cd.ContractType == typeof (IMetadataExchange))
                                        continue;
 
@@ -290,7 +282,7 @@ namespace System.ServiceModel
                        ContractDescription cd, Binding binding, EndpointAddress address, Uri listenUri)
                {
                        foreach (ServiceEndpoint e in Description.Endpoints)
-                               if (e.Contract == cd)
+                               if (e.Contract == cd && e.Binding == binding && e.Address == address && e.ListenUri.Equals (listenUri))
                                        return e;
                        ServiceEndpoint se = new ServiceEndpoint (cd, binding, address);
                        se.ListenUri = listenUri.IsAbsoluteUri ? listenUri : new Uri (address.Uri, listenUri);
@@ -298,7 +290,6 @@ namespace System.ServiceModel
                        return se;
                }
 
-               [MonoTODO]
                protected virtual void ApplyConfiguration ()
                {
                        if (Description == null)
@@ -327,7 +318,6 @@ namespace System.ServiceModel
 
                                // services
                                foreach (ServiceEndpointElement endpoint in service.Endpoints) {
-                                       // FIXME: consider BindingName as well
                                        ServiceEndpoint se = AddServiceEndpoint (
                                                endpoint.Contract,
                                                ConfigUtil.CreateBinding (endpoint.Binding, endpoint.BindingConfiguration),
@@ -388,18 +378,18 @@ namespace System.ServiceModel
                        //Build all ChannelDispatchers, one dispatcher per user configured EndPoint.
                        //We must keep thet ServiceEndpoints as a seperate collection, since the user
                        //can change the collection in the description during the behaviors events.
-                       Dictionary<ServiceEndpoint, ChannelDispatcher> endPointToDispatcher = new Dictionary<ServiceEndpoint,ChannelDispatcher>();
                        ServiceEndpoint[] endPoints = new ServiceEndpoint[Description.Endpoints.Count];
                        Description.Endpoints.CopyTo (endPoints, 0);
+                       var builder = new DispatcherBuilder (this);
                        foreach (ServiceEndpoint se in endPoints) {
 
                                var commonParams = new BindingParameterCollection ();
                                foreach (IServiceBehavior b in Description.Behaviors)
                                        b.AddBindingParameters (Description, this, Description.Endpoints, commonParams);
 
-                               var channel = new DispatcherBuilder ().BuildChannelDispatcher (Description.ServiceType, se, commonParams);
-                               ChannelDispatchers.Add (channel);
-                               endPointToDispatcher[se] = channel;
+                               var channel = builder.BuildChannelDispatcher (Description.ServiceType, se, commonParams);
+                               if (!ChannelDispatchers.Contains (channel))
+                                       ChannelDispatchers.Add (channel);
                        }
 
                        //After the ChannelDispatchers are created, and attached to the service host
@@ -407,9 +397,7 @@ namespace System.ServiceModel
                        foreach (IServiceBehavior b in Description.Behaviors)
                                b.ApplyDispatchBehavior (Description, this);
 
-                       foreach(KeyValuePair<ServiceEndpoint, ChannelDispatcher> val in endPointToDispatcher)
-                               foreach (var ed in val.Value.Endpoints)
-                                       ApplyDispatchBehavior (ed, val.Key);                    
+                       builder.ApplyDispatchBehaviors ();
                }
 
                private void ValidateDescription ()
@@ -423,19 +411,6 @@ namespace System.ServiceModel
                                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.");
                }
 
-               private void ApplyDispatchBehavior (EndpointDispatcher ed, ServiceEndpoint endPoint)
-               {
-                       foreach (IContractBehavior b in endPoint.Contract.Behaviors)
-                               b.ApplyDispatchBehavior (endPoint.Contract, endPoint, ed.DispatchRuntime);
-                       foreach (IEndpointBehavior b in endPoint.Behaviors)
-                               b.ApplyDispatchBehavior (endPoint, ed);
-                       foreach (OperationDescription operation in endPoint.Contract.Operations) {
-                               foreach (IOperationBehavior b in operation.Behaviors)
-                                       b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
-                       }
-
-               }
-
                [MonoTODO]
                protected void LoadConfigurationSection (ServiceElement element)
                {
@@ -609,19 +584,76 @@ namespace System.ServiceModel
                */
        }
 
+       /// <summary>
+       ///  Builds ChannelDispatchers as appropriate to service the service endpoints. 
+       /// </summary>
+       /// <remarks>Will re-use ChannelDispatchers when two endpoint uris are the same</remarks>
        partial class DispatcherBuilder
        {
+               ServiceHostBase host;
+
+               public DispatcherBuilder (ServiceHostBase host)
+               {
+                       this.host = host;
+               }
+
+               List<ChannelDispatcher> built_dispatchers = new List<ChannelDispatcher> ();
+               Dictionary<ServiceEndpoint, EndpointDispatcher> ep_to_dispatcher_ep = new Dictionary<ServiceEndpoint, EndpointDispatcher> ();
+
+               internal static Action<ChannelDispatcher> ChannelDispatcherSetter;
+
                internal ChannelDispatcher BuildChannelDispatcher (Type serviceType, ServiceEndpoint se, BindingParameterCollection commonParams)
                {
                        //Let all behaviors add their binding parameters
                        AddBindingParameters (commonParams, se);
-                       //User the binding parameters to build the channel listener and Dispatcher
-                       IChannelListener lf = BuildListener (se, commonParams);
-                       ChannelDispatcher cd = new ChannelDispatcher (
-                               lf, se.Binding.Name);
-                       cd.InitializeServiceEndpoint (serviceType, se);
+                       
+                       // See if there's an existing channel that matches this endpoint
+                       ChannelDispatcher cd = FindExistingDispatcher (se);
+                       EndpointDispatcher ep;
+                       if (cd != null) {
+                               ep = cd.InitializeServiceEndpoint (serviceType, se);
+                       } else {
+                               // Use the binding parameters to build the channel listener and Dispatcher.
+                               lock (HttpTransportBindingElement.ListenerBuildLock) {
+                                       ServiceHostBase.CurrentServiceHostHack = host;
+                                       IChannelListener lf = BuildListener (se, commonParams);
+                                       cd = new ChannelDispatcher (lf, se.Binding.Name);
+                                       if (ChannelDispatcherSetter != null) {
+                                               ChannelDispatcherSetter (cd);
+                                               ChannelDispatcherSetter = null;
+                                       }
+                                       ServiceHostBase.CurrentServiceHostHack = null;
+                               }
+                               ep = cd.InitializeServiceEndpoint (serviceType, se);
+                               built_dispatchers.Add (cd);
+                       }
+                       ep_to_dispatcher_ep[se] = ep;
                        return cd;
                }
+               
+               ChannelDispatcher FindExistingDispatcher (ServiceEndpoint se)
+               {
+                       return built_dispatchers.FirstOrDefault ((ChannelDispatcher cd) => (cd.Listener.Uri.Equals (se.ListenUri)) && cd.MessageVersion.Equals (se.Binding.MessageVersion));
+               }
+
+               internal void ApplyDispatchBehaviors ()
+               {
+                       foreach (KeyValuePair<ServiceEndpoint, EndpointDispatcher> val in ep_to_dispatcher_ep)
+                               ApplyDispatchBehavior (val.Value, val.Key);
+               }
+               
+               private void ApplyDispatchBehavior (EndpointDispatcher ed, ServiceEndpoint endPoint)
+               {
+                       foreach (IContractBehavior b in endPoint.Contract.Behaviors)
+                               b.ApplyDispatchBehavior (endPoint.Contract, endPoint, ed.DispatchRuntime);
+                       foreach (IEndpointBehavior b in endPoint.Behaviors)
+                               b.ApplyDispatchBehavior (endPoint, ed);
+                       foreach (OperationDescription operation in endPoint.Contract.Operations) {
+                               foreach (IOperationBehavior b in operation.Behaviors)
+                                       b.ApplyDispatchBehavior (operation, ed.DispatchRuntime.Operations [operation.Name]);
+                       }
+
+               }
 
                private void AddBindingParameters (BindingParameterCollection commonParams, ServiceEndpoint endPoint) {